feat(governance): 新增全產品 Code Review 防木馬 Gate
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m49s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m49s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
This commit is contained in:
@@ -319,6 +319,9 @@ from src.services.offsite_escrow_readiness_status import (
|
||||
from src.services.package_supply_chain_inventory import (
|
||||
load_latest_package_supply_chain_inventory,
|
||||
)
|
||||
from src.services.product_code_review_gate import (
|
||||
load_latest_product_code_review_gate,
|
||||
)
|
||||
from src.services.runtime_surface_inventory import (
|
||||
load_latest_runtime_surface_inventory,
|
||||
)
|
||||
@@ -3388,6 +3391,34 @@ async def get_dependency_supply_chain_drift_monitor() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/product-code-review-gate",
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 P2-111 全產品 Code Review 防木馬 Gate",
|
||||
description=(
|
||||
"讀取最新已提交的 P2-111 全產品推版前後 Code Review / 防木馬 Gate;"
|
||||
"此端點會把 AwoooP tenants 資產台帳、Gitea code-review、供應鏈漂移、Aider 事件與 AI reviewer "
|
||||
"分工收斂成只讀 read model。它不啟用外部 scanner、不寫 workflow、不 auto-merge、"
|
||||
"不部署、不讀 secret、不推 registry、不簽 artifact、不送 Telegram、不寫 Gateway queue、不做 host probe。"
|
||||
),
|
||||
)
|
||||
async def get_product_code_review_gate() -> dict[str, Any]:
|
||||
"""Return the latest read-only all-product code review gate."""
|
||||
try:
|
||||
return await asyncio.to_thread(load_latest_product_code_review_gate)
|
||||
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("product_code_review_gate_invalid", error=str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="P2-111 全產品 Code Review Gate 快照無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/dependency-upgrade-approval-package-template",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
193
apps/api/src/services/product_code_review_gate.py
Normal file
193
apps/api/src/services/product_code_review_gate.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
Product code review gate snapshot.
|
||||
|
||||
Loads the latest committed, read-only all-product code-review gate and enriches
|
||||
it with the existing AwoooP tenant asset inventory. The loader never enables
|
||||
external scanners, writes workflows, auto-merges code, deploys, reads secrets,
|
||||
pushes registry artifacts, sends Telegram messages, or opens runtime gates.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from src.services.platform_operator_service import build_tenant_asset_inventory
|
||||
from src.services.snapshot_paths import default_evaluations_dir
|
||||
|
||||
_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__))
|
||||
_SNAPSHOT_PATTERN = "product_code_review_gate_*.json"
|
||||
_SCHEMA_VERSION = "product_code_review_gate_v1"
|
||||
|
||||
_BLOCKED_BOUNDARY_FLAGS = {
|
||||
"workflow_write_allowed",
|
||||
"external_scanner_activation_allowed",
|
||||
"paid_ai_review_allowed",
|
||||
"repo_app_install_allowed",
|
||||
"auto_merge_allowed",
|
||||
"production_deploy_authorized",
|
||||
"aider_auto_patch_allowed",
|
||||
"elephantalpha_write_allowed",
|
||||
"secret_read_allowed",
|
||||
"post_deploy_write_allowed",
|
||||
"runtime_execution_allowed",
|
||||
"telegram_send_allowed",
|
||||
"gateway_queue_write_allowed",
|
||||
"host_probe_allowed",
|
||||
"registry_push_allowed",
|
||||
"artifact_signing_allowed",
|
||||
"action_buttons_allowed",
|
||||
}
|
||||
|
||||
|
||||
def load_latest_product_code_review_gate(
|
||||
evaluations_dir: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Load the newest committed all-product code-review gate snapshot."""
|
||||
directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
|
||||
candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
|
||||
if not candidates:
|
||||
raise FileNotFoundError(f"no product code review gate snapshots found in {directory}")
|
||||
|
||||
latest = candidates[-1]
|
||||
with latest.open(encoding="utf-8") as handle:
|
||||
payload = json.load(handle)
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError(f"{latest}: expected JSON object")
|
||||
|
||||
inventory = build_tenant_asset_inventory([])
|
||||
enriched = deepcopy(payload)
|
||||
enriched["tenant_asset_inventory_summary"] = inventory.get("summary") or {}
|
||||
enriched["product_review_matrix"] = _build_product_review_matrix(inventory)
|
||||
|
||||
_require_schema(enriched, _SCHEMA_VERSION, str(latest))
|
||||
_require_read_only_boundaries(enriched, str(latest))
|
||||
_require_rollup_consistency(enriched, str(latest))
|
||||
_require_gate_evidence(enriched, str(latest))
|
||||
_require_agent_boundaries(enriched, str(latest))
|
||||
return enriched
|
||||
|
||||
|
||||
def _build_product_review_matrix(inventory: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
products = inventory.get("products") or []
|
||||
matrix: list[dict[str, Any]] = []
|
||||
for product in products:
|
||||
status = product.get("coverage_status") or "read_only_candidate"
|
||||
source_repo_count = int(product.get("source_repo_count") or 0)
|
||||
public_route_count = int(product.get("public_route_count") or 0)
|
||||
gate_state = "owner_review_required" if status == "owner_response_required" else "read_only_visible"
|
||||
if source_repo_count == 0:
|
||||
gate_state = "source_mapping_required"
|
||||
matrix.append(
|
||||
{
|
||||
"product_id": product.get("product_id"),
|
||||
"product_name": product.get("product_name"),
|
||||
"category": product.get("category"),
|
||||
"surface_kind": product.get("surface_kind"),
|
||||
"owner_lane": product.get("owner_lane"),
|
||||
"coverage_status": status,
|
||||
"public_route_count": public_route_count,
|
||||
"source_repo_count": source_repo_count,
|
||||
"gate_state": gate_state,
|
||||
"pre_deploy_gate": "required",
|
||||
"post_deploy_gate": "required",
|
||||
"owner_response_received_count": int(
|
||||
product.get("owner_response_received_count") or 0
|
||||
),
|
||||
"owner_response_accepted_count": int(
|
||||
product.get("owner_response_accepted_count") or 0
|
||||
),
|
||||
"runtime_gate_count": int(product.get("runtime_gate_count") or 0),
|
||||
"action_button_count": int(product.get("action_button_count") or 0),
|
||||
}
|
||||
)
|
||||
return matrix
|
||||
|
||||
|
||||
def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None:
|
||||
actual = payload.get("schema_version")
|
||||
if actual != expected:
|
||||
raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}")
|
||||
|
||||
|
||||
def _require_read_only_boundaries(payload: dict[str, Any], label: str) -> None:
|
||||
program_status = payload.get("program_status") or {}
|
||||
if program_status.get("read_only_mode") is not True:
|
||||
raise ValueError(f"{label}: program_status.read_only_mode must be true")
|
||||
|
||||
boundaries = payload.get("gate_boundaries") or {}
|
||||
if boundaries.get("read_only_api_allowed") is not True:
|
||||
raise ValueError(f"{label}: read_only_api_allowed must be true")
|
||||
|
||||
allowed = sorted(flag for flag in _BLOCKED_BOUNDARY_FLAGS if boundaries.get(flag) is not False)
|
||||
if allowed:
|
||||
raise ValueError(f"{label}: gate boundaries must remain false: {allowed}")
|
||||
|
||||
|
||||
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
|
||||
rollups = payload.get("rollups") or {}
|
||||
tenant_summary = payload.get("tenant_asset_inventory_summary") or {}
|
||||
product_matrix = payload.get("product_review_matrix") or []
|
||||
pre_deploy = payload.get("pre_deploy_gates") or []
|
||||
post_deploy = payload.get("post_deploy_gates") or []
|
||||
reviewers = payload.get("ai_reviewer_lanes") or []
|
||||
tools = payload.get("mainstream_tool_lanes") or []
|
||||
boundaries = payload.get("gate_boundaries") or {}
|
||||
|
||||
product_count = int(tenant_summary.get("product_surface_count") or 0)
|
||||
route_count = int(tenant_summary.get("public_route_count") or 0)
|
||||
source_count = int(tenant_summary.get("source_candidate_repo_count") or 0)
|
||||
|
||||
if rollups.get("product_scope_count") != product_count:
|
||||
raise ValueError(f"{label}: rollups.product_scope_count must match tenant inventory")
|
||||
if rollups.get("product_scope_count") != len(product_matrix):
|
||||
raise ValueError(f"{label}: product_review_matrix length must match product scope count")
|
||||
if route_count < int(rollups.get("public_route_count_minimum") or 0):
|
||||
raise ValueError(f"{label}: tenant route count is lower than product gate minimum")
|
||||
if rollups.get("source_candidate_repo_count") != source_count:
|
||||
raise ValueError(f"{label}: source candidate count must match tenant inventory")
|
||||
if rollups.get("pre_deploy_gate_count") != len(pre_deploy):
|
||||
raise ValueError(f"{label}: pre_deploy_gate_count must match gates")
|
||||
if rollups.get("post_deploy_gate_count") != len(post_deploy):
|
||||
raise ValueError(f"{label}: post_deploy_gate_count must match gates")
|
||||
if rollups.get("ai_reviewer_count") != len(reviewers):
|
||||
raise ValueError(f"{label}: ai_reviewer_count must match reviewer lanes")
|
||||
if rollups.get("mainstream_tool_count") != len(tools):
|
||||
raise ValueError(f"{label}: mainstream_tool_count must match tool lanes")
|
||||
|
||||
false_count = sum(1 for flag in _BLOCKED_BOUNDARY_FLAGS if boundaries.get(flag) is False)
|
||||
if rollups.get("blocked_operation_count") != false_count:
|
||||
raise ValueError(f"{label}: blocked_operation_count must match false boundaries")
|
||||
if rollups.get("active_write_gate_count") != 0:
|
||||
raise ValueError(f"{label}: active_write_gate_count must remain 0")
|
||||
if rollups.get("action_button_count") != 0:
|
||||
raise ValueError(f"{label}: action_button_count must remain 0")
|
||||
|
||||
|
||||
def _require_gate_evidence(payload: dict[str, Any], label: str) -> None:
|
||||
for key in ("pre_deploy_gates", "post_deploy_gates"):
|
||||
for gate in payload.get(key) or []:
|
||||
gate_id = gate.get("gate_id") or "<missing>"
|
||||
if not gate.get("evidence_refs"):
|
||||
raise ValueError(f"{label}: gate {gate_id} must include evidence_refs")
|
||||
if not gate.get("next_action"):
|
||||
raise ValueError(f"{label}: gate {gate_id} must include next_action")
|
||||
|
||||
|
||||
def _require_agent_boundaries(payload: dict[str, Any], label: str) -> None:
|
||||
reviewers = payload.get("ai_reviewer_lanes") or []
|
||||
for reviewer in reviewers:
|
||||
agent_id = reviewer.get("agent_id") or "<missing>"
|
||||
if reviewer.get("write_allowed") is not False:
|
||||
raise ValueError(f"{label}: reviewer {agent_id} write_allowed must remain false")
|
||||
|
||||
matrix = payload.get("product_review_matrix") or []
|
||||
active_runtime = [item for item in matrix if item.get("runtime_gate_count") != 0]
|
||||
action_buttons = [item for item in matrix if item.get("action_button_count") != 0]
|
||||
if active_runtime:
|
||||
raise ValueError(f"{label}: product runtime gates must remain 0")
|
||||
if action_buttons:
|
||||
raise ValueError(f"{label}: product action buttons must remain 0")
|
||||
199
apps/api/tests/test_product_code_review_gate.py
Normal file
199
apps/api/tests/test_product_code_review_gate.py
Normal file
@@ -0,0 +1,199 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from src.services.product_code_review_gate import load_latest_product_code_review_gate
|
||||
|
||||
|
||||
def test_load_latest_product_code_review_gate_enriches_tenant_inventory(tmp_path):
|
||||
_write_snapshot(tmp_path, _snapshot())
|
||||
|
||||
loaded = load_latest_product_code_review_gate(tmp_path)
|
||||
|
||||
assert loaded["schema_version"] == "product_code_review_gate_v1"
|
||||
assert loaded["program_status"]["current_task_id"] == "P2-111"
|
||||
assert loaded["program_status"]["next_task_id"] == "P2-112"
|
||||
assert loaded["program_status"]["read_only_mode"] is True
|
||||
assert loaded["rollups"]["product_scope_count"] == len(loaded["product_review_matrix"]) >= 16
|
||||
assert loaded["tenant_asset_inventory_summary"]["public_route_count"] >= 31
|
||||
assert loaded["rollups"]["source_candidate_repo_count"] == 10
|
||||
assert loaded["gate_boundaries"]["auto_merge_allowed"] is False
|
||||
assert loaded["gate_boundaries"]["aider_auto_patch_allowed"] is False
|
||||
assert loaded["gate_boundaries"]["elephantalpha_write_allowed"] is False
|
||||
assert all(item["runtime_gate_count"] == 0 for item in loaded["product_review_matrix"])
|
||||
assert all(item["action_button_count"] == 0 for item in loaded["product_review_matrix"])
|
||||
assert any(item["product_id"] == "PRD-001" for item in loaded["product_review_matrix"])
|
||||
assert any(tool["tool_id"] == "codeql" for tool in loaded["mainstream_tool_lanes"])
|
||||
assert any(tool["tool_id"] == "gitleaks" for tool in loaded["mainstream_tool_lanes"])
|
||||
|
||||
|
||||
def test_product_code_review_gate_requires_read_only_mode(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["program_status"]["read_only_mode"] = False
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="read_only_mode"):
|
||||
load_latest_product_code_review_gate(tmp_path)
|
||||
|
||||
|
||||
def test_product_code_review_gate_blocks_auto_merge(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["gate_boundaries"]["auto_merge_allowed"] = True
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="gate boundaries"):
|
||||
load_latest_product_code_review_gate(tmp_path)
|
||||
|
||||
|
||||
def test_product_code_review_gate_requires_rollup_consistency(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["rollups"]["pre_deploy_gate_count"] = 99
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="pre_deploy_gate_count"):
|
||||
load_latest_product_code_review_gate(tmp_path)
|
||||
|
||||
|
||||
def test_product_code_review_gate_requires_gate_evidence(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["pre_deploy_gates"][0]["evidence_refs"] = []
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="evidence_refs"):
|
||||
load_latest_product_code_review_gate(tmp_path)
|
||||
|
||||
|
||||
def test_product_code_review_gate_requires_reviewer_no_write(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["ai_reviewer_lanes"][0]["write_allowed"] = True
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="write_allowed"):
|
||||
load_latest_product_code_review_gate(tmp_path)
|
||||
|
||||
|
||||
def test_product_code_review_gate_fails_when_missing(tmp_path):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
load_latest_product_code_review_gate(tmp_path)
|
||||
|
||||
|
||||
def _write_snapshot(tmp_path, payload: dict) -> None:
|
||||
(tmp_path / "product_code_review_gate_2026-06-19.json").write_text(
|
||||
json.dumps(payload),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _snapshot() -> dict:
|
||||
return {
|
||||
"schema_version": "product_code_review_gate_v1",
|
||||
"generated_at": "2026-06-19T00:42:00+08:00",
|
||||
"program_status": {
|
||||
"overall_completion_percent": 100,
|
||||
"current_priority": "P2",
|
||||
"current_task_id": "P2-111",
|
||||
"next_task_id": "P2-112",
|
||||
"read_only_mode": True,
|
||||
"runtime_authority": "repo_only",
|
||||
"status_note": "只讀 Code Review Gate。",
|
||||
},
|
||||
"source_refs": [".gitea/workflows/code-review.yaml"],
|
||||
"rollups": {
|
||||
"product_scope_count": 16,
|
||||
"public_route_count_minimum": 31,
|
||||
"source_candidate_repo_count": 10,
|
||||
"pre_deploy_gate_count": 1,
|
||||
"post_deploy_gate_count": 1,
|
||||
"ai_reviewer_count": 2,
|
||||
"mainstream_tool_count": 2,
|
||||
"owner_review_required_count": 1,
|
||||
"critical_gap_count": 1,
|
||||
"blocked_operation_count": 17,
|
||||
"active_write_gate_count": 0,
|
||||
"action_button_count": 0,
|
||||
},
|
||||
"pre_deploy_gates": [_gate("pre", "hermes")],
|
||||
"post_deploy_gates": [_gate("post", "nemotron")],
|
||||
"ai_reviewer_lanes": [
|
||||
_reviewer("hermes"),
|
||||
_reviewer("aider"),
|
||||
],
|
||||
"mainstream_tool_lanes": [
|
||||
_tool("codeql"),
|
||||
_tool("gitleaks"),
|
||||
],
|
||||
"decision_matrix": [
|
||||
{
|
||||
"risk_lane": "low",
|
||||
"reviewer": "Hermes",
|
||||
"aider_role": "draft",
|
||||
"required_gate": "owner",
|
||||
"post_deploy": "smoke",
|
||||
}
|
||||
],
|
||||
"gate_boundaries": {
|
||||
"read_only_api_allowed": True,
|
||||
"workflow_write_allowed": False,
|
||||
"external_scanner_activation_allowed": False,
|
||||
"paid_ai_review_allowed": False,
|
||||
"repo_app_install_allowed": False,
|
||||
"auto_merge_allowed": False,
|
||||
"production_deploy_authorized": False,
|
||||
"aider_auto_patch_allowed": False,
|
||||
"elephantalpha_write_allowed": False,
|
||||
"secret_read_allowed": False,
|
||||
"post_deploy_write_allowed": False,
|
||||
"runtime_execution_allowed": False,
|
||||
"telegram_send_allowed": False,
|
||||
"gateway_queue_write_allowed": False,
|
||||
"host_probe_allowed": False,
|
||||
"registry_push_allowed": False,
|
||||
"artifact_signing_allowed": False,
|
||||
"action_buttons_allowed": False,
|
||||
},
|
||||
"next_actions": [
|
||||
{
|
||||
"task_id": "P2-112",
|
||||
"priority": "P1",
|
||||
"summary": "下一步",
|
||||
"gate": "owner",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _gate(gate_id: str, owner_agent: str) -> dict:
|
||||
return {
|
||||
"gate_id": gate_id,
|
||||
"label": gate_id,
|
||||
"coverage": "repo",
|
||||
"status": "wired",
|
||||
"owner_agent": owner_agent,
|
||||
"evidence_refs": ["docs/LOGBOOK.md"],
|
||||
"current_gap": "gap",
|
||||
"next_action": "review",
|
||||
}
|
||||
|
||||
|
||||
def _reviewer(agent_id: str) -> dict:
|
||||
return {
|
||||
"agent_id": agent_id,
|
||||
"label": agent_id,
|
||||
"role": "review",
|
||||
"allowed_output": "packet",
|
||||
"write_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def _tool(tool_id: str) -> dict:
|
||||
return {
|
||||
"tool_id": tool_id,
|
||||
"label": tool_id,
|
||||
"category": "scanner",
|
||||
"source_url": "https://example.com",
|
||||
"integration_status": "candidate",
|
||||
"recommended_role": "scan",
|
||||
"blocked_now": ["external lookup"],
|
||||
}
|
||||
38
apps/api/tests/test_product_code_review_gate_api.py
Normal file
38
apps/api/tests/test_product_code_review_gate_api.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1.agents import router
|
||||
|
||||
|
||||
def test_product_code_review_gate_endpoint_returns_all_product_gate():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/agents/product-code-review-gate")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "product_code_review_gate_v1"
|
||||
assert data["program_status"]["current_task_id"] == "P2-111"
|
||||
assert data["program_status"]["next_task_id"] == "P2-112"
|
||||
assert data["program_status"]["read_only_mode"] is True
|
||||
assert data["rollups"]["product_scope_count"] == len(data["product_review_matrix"]) >= 16
|
||||
assert data["tenant_asset_inventory_summary"]["public_route_count"] >= 31
|
||||
assert data["rollups"]["pre_deploy_gate_count"] == len(data["pre_deploy_gates"]) == 8
|
||||
assert data["rollups"]["post_deploy_gate_count"] == len(data["post_deploy_gates"]) == 6
|
||||
assert data["rollups"]["ai_reviewer_count"] == len(data["ai_reviewer_lanes"]) == 5
|
||||
assert data["rollups"]["mainstream_tool_count"] == len(data["mainstream_tool_lanes"]) == 9
|
||||
assert data["rollups"]["active_write_gate_count"] == 0
|
||||
assert data["rollups"]["action_button_count"] == 0
|
||||
assert data["gate_boundaries"]["auto_merge_allowed"] is False
|
||||
assert data["gate_boundaries"]["aider_auto_patch_allowed"] is False
|
||||
assert data["gate_boundaries"]["external_scanner_activation_allowed"] is False
|
||||
assert data["gate_boundaries"]["artifact_signing_allowed"] is False
|
||||
assert any(product["product_id"] == "PRD-001" for product in data["product_review_matrix"])
|
||||
assert any(product["product_id"] == "PRD-006" for product in data["product_review_matrix"])
|
||||
assert any(tool["tool_id"] == "codeql" for tool in data["mainstream_tool_lanes"])
|
||||
assert any(tool["tool_id"] == "semgrep" for tool in data["mainstream_tool_lanes"])
|
||||
assert any(tool["tool_id"] == "osv_scanner" for tool in data["mainstream_tool_lanes"])
|
||||
@@ -213,6 +213,90 @@
|
||||
"subtitle": "Hermes、OpenClaw、Elephant Alpha 與 NemoTron 只做審查、分級與候選整理;修正、推版與主機操作仍留在人工閘門後面。",
|
||||
"evidenceLink": "查看 AwoooP 執行紀錄"
|
||||
},
|
||||
"gateBoard": {
|
||||
"eyebrow": "全產品防木馬 Gate",
|
||||
"title": "推版前後 Code Review 總控",
|
||||
"subtitle": "把 Tenants 全產品資產、Gitea 審查、供應鏈漂移、Aider 事件、ElephantAlpha 高風險審查與主流掃描工具候選收斂成同一張只讀 Gate;目前只呈現證據與缺口,不 auto-merge、不自動部署、不讀 secret。",
|
||||
"loading": "讀取全產品 Code Review Gate",
|
||||
"unavailable": "暫時無法讀取全產品 Code Review Gate;請查看 API / Gitea Actions readback。",
|
||||
"metrics": {
|
||||
"current": "目前任務",
|
||||
"next": "下一步",
|
||||
"writeGate": "寫入閘門",
|
||||
"actions": "操作入口"
|
||||
},
|
||||
"metricCards": {
|
||||
"products": {
|
||||
"label": "產品 / 專案",
|
||||
"detail": "沿用 Tenants 全域資產台帳。"
|
||||
},
|
||||
"routes": {
|
||||
"label": "網站入口",
|
||||
"detail": "公開前台、後台與平台工具入口。"
|
||||
},
|
||||
"pre": {
|
||||
"label": "推版前 Gate",
|
||||
"detail": "diff、secret、供應鏈、owner gate。"
|
||||
},
|
||||
"post": {
|
||||
"label": "推版後 Gate",
|
||||
"detail": "marker、smoke、rollback、verifier。"
|
||||
},
|
||||
"reviewers": {
|
||||
"label": "AI Reviewer",
|
||||
"detail": "Hermes / OpenClaw / ElephantAlpha / NemoTron / Aider。"
|
||||
},
|
||||
"tools": {
|
||||
"label": "主流工具候選",
|
||||
"detail": "CodeQL、Semgrep、Gitleaks、OSV、Trivy 等。"
|
||||
}
|
||||
},
|
||||
"flow": {
|
||||
"assets": {
|
||||
"title": "資產範圍",
|
||||
"detail": "所有網站、產品、工具先進同一張資產台帳。"
|
||||
},
|
||||
"preDeploy": {
|
||||
"title": "推版前",
|
||||
"detail": "先做 diff、secret、供應鏈與高風險審查。"
|
||||
},
|
||||
"aiReview": {
|
||||
"title": "AI 審查",
|
||||
"detail": "ElephantAlpha 只讀 hold;Aider 只在批准後起草。"
|
||||
},
|
||||
"postDeploy": {
|
||||
"title": "推版後",
|
||||
"detail": "驗證 marker、路由、回滾與 verifier receipt。"
|
||||
},
|
||||
"ownerGate": {
|
||||
"title": "Owner Gate",
|
||||
"detail": "高風險進人工接受;結果回寫 KM / PlayBook。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"products": {
|
||||
"title": "產品 / 專案 Code Review 覆蓋矩陣",
|
||||
"subtitle": "每個產品都必須有推版前與推版後 Gate;來源缺口或負責人未接受時,只能列為候選。",
|
||||
"labels": {
|
||||
"routes": "入口",
|
||||
"sources": "來源",
|
||||
"gates": "閘門"
|
||||
},
|
||||
"states": {
|
||||
"visible": "已納管",
|
||||
"ownerReview": "待 owner",
|
||||
"sourceMapping": "待來源"
|
||||
}
|
||||
},
|
||||
"prepost": {
|
||||
"title": "推版前 / 推版後 Gate",
|
||||
"pre": "Pre-deploy",
|
||||
"post": "Post-deploy"
|
||||
},
|
||||
"tools": {
|
||||
"title": "主流工具與 AI 工具整合候選",
|
||||
"candidate": "需批准"
|
||||
},
|
||||
"handoff": {
|
||||
"eyebrow": "Code Review → Codex",
|
||||
"title": "審查後 Coding 工作橋接",
|
||||
|
||||
@@ -213,6 +213,90 @@
|
||||
"subtitle": "Hermes、OpenClaw、Elephant Alpha 與 NemoTron 只做審查、分級與候選整理;修正、推版與主機操作仍留在人工閘門後面。",
|
||||
"evidenceLink": "查看 AwoooP 執行紀錄"
|
||||
},
|
||||
"gateBoard": {
|
||||
"eyebrow": "全產品防木馬 Gate",
|
||||
"title": "推版前後 Code Review 總控",
|
||||
"subtitle": "把 Tenants 全產品資產、Gitea 審查、供應鏈漂移、Aider 事件、ElephantAlpha 高風險審查與主流掃描工具候選收斂成同一張只讀 Gate;目前只呈現證據與缺口,不 auto-merge、不自動部署、不讀 secret。",
|
||||
"loading": "讀取全產品 Code Review Gate",
|
||||
"unavailable": "暫時無法讀取全產品 Code Review Gate;請查看 API / Gitea Actions readback。",
|
||||
"metrics": {
|
||||
"current": "目前任務",
|
||||
"next": "下一步",
|
||||
"writeGate": "寫入閘門",
|
||||
"actions": "操作入口"
|
||||
},
|
||||
"metricCards": {
|
||||
"products": {
|
||||
"label": "產品 / 專案",
|
||||
"detail": "沿用 Tenants 全域資產台帳。"
|
||||
},
|
||||
"routes": {
|
||||
"label": "網站入口",
|
||||
"detail": "公開前台、後台與平台工具入口。"
|
||||
},
|
||||
"pre": {
|
||||
"label": "推版前 Gate",
|
||||
"detail": "diff、secret、供應鏈、owner gate。"
|
||||
},
|
||||
"post": {
|
||||
"label": "推版後 Gate",
|
||||
"detail": "marker、smoke、rollback、verifier。"
|
||||
},
|
||||
"reviewers": {
|
||||
"label": "AI Reviewer",
|
||||
"detail": "Hermes / OpenClaw / ElephantAlpha / NemoTron / Aider。"
|
||||
},
|
||||
"tools": {
|
||||
"label": "主流工具候選",
|
||||
"detail": "CodeQL、Semgrep、Gitleaks、OSV、Trivy 等。"
|
||||
}
|
||||
},
|
||||
"flow": {
|
||||
"assets": {
|
||||
"title": "資產範圍",
|
||||
"detail": "所有網站、產品、工具先進同一張資產台帳。"
|
||||
},
|
||||
"preDeploy": {
|
||||
"title": "推版前",
|
||||
"detail": "先做 diff、secret、供應鏈與高風險審查。"
|
||||
},
|
||||
"aiReview": {
|
||||
"title": "AI 審查",
|
||||
"detail": "ElephantAlpha 只讀 hold;Aider 只在批准後起草。"
|
||||
},
|
||||
"postDeploy": {
|
||||
"title": "推版後",
|
||||
"detail": "驗證 marker、路由、回滾與 verifier receipt。"
|
||||
},
|
||||
"ownerGate": {
|
||||
"title": "Owner Gate",
|
||||
"detail": "高風險進人工接受;結果回寫 KM / PlayBook。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"products": {
|
||||
"title": "產品 / 專案 Code Review 覆蓋矩陣",
|
||||
"subtitle": "每個產品都必須有推版前與推版後 Gate;來源缺口或負責人未接受時,只能列為候選。",
|
||||
"labels": {
|
||||
"routes": "入口",
|
||||
"sources": "來源",
|
||||
"gates": "閘門"
|
||||
},
|
||||
"states": {
|
||||
"visible": "已納管",
|
||||
"ownerReview": "待 owner",
|
||||
"sourceMapping": "待來源"
|
||||
}
|
||||
},
|
||||
"prepost": {
|
||||
"title": "推版前 / 推版後 Gate",
|
||||
"pre": "Pre-deploy",
|
||||
"post": "Post-deploy"
|
||||
},
|
||||
"tools": {
|
||||
"title": "主流工具與 AI 工具整合候選",
|
||||
"candidate": "需批准"
|
||||
},
|
||||
"handoff": {
|
||||
"eyebrow": "Code Review → Codex",
|
||||
"title": "審查後 Coding 工作橋接",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { AppLayout } from '@/components/layout'
|
||||
import { IwoooSReadOnlyBridge } from '@/components/security/iwooos-read-only-bridge'
|
||||
import { publicBoundaryText } from '@/lib/public-security-redaction'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Activity,
|
||||
ArrowRight,
|
||||
@@ -13,15 +14,92 @@ import {
|
||||
ExternalLink,
|
||||
FileCheck2,
|
||||
GitBranch,
|
||||
Globe2,
|
||||
Gauge,
|
||||
ListChecks,
|
||||
LockKeyhole,
|
||||
PackageSearch,
|
||||
SearchCheck,
|
||||
ShieldAlert,
|
||||
ShieldCheck,
|
||||
Workflow,
|
||||
} from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
|
||||
type ProductCodeReviewGateSnapshot = {
|
||||
schema_version: string
|
||||
generated_at: string
|
||||
program_status: {
|
||||
overall_completion_percent: number
|
||||
current_task_id: string
|
||||
next_task_id: string
|
||||
read_only_mode: boolean
|
||||
}
|
||||
rollups: {
|
||||
product_scope_count: number
|
||||
public_route_count_minimum: number
|
||||
source_candidate_repo_count: number
|
||||
pre_deploy_gate_count: number
|
||||
post_deploy_gate_count: number
|
||||
ai_reviewer_count: number
|
||||
mainstream_tool_count: number
|
||||
owner_review_required_count: number
|
||||
critical_gap_count: number
|
||||
active_write_gate_count: number
|
||||
action_button_count: number
|
||||
}
|
||||
tenant_asset_inventory_summary: {
|
||||
public_route_count?: number
|
||||
source_candidate_repo_count?: number
|
||||
}
|
||||
product_review_matrix: Array<{
|
||||
product_id: string
|
||||
product_name: string
|
||||
category: string
|
||||
owner_lane: string
|
||||
coverage_status: string
|
||||
public_route_count: number
|
||||
source_repo_count: number
|
||||
gate_state: string
|
||||
runtime_gate_count: number
|
||||
action_button_count: number
|
||||
}>
|
||||
pre_deploy_gates: Array<{
|
||||
gate_id: string
|
||||
label: string
|
||||
status: string
|
||||
owner_agent: string
|
||||
current_gap: string
|
||||
next_action: string
|
||||
}>
|
||||
post_deploy_gates: Array<{
|
||||
gate_id: string
|
||||
label: string
|
||||
status: string
|
||||
owner_agent: string
|
||||
current_gap: string
|
||||
next_action: string
|
||||
}>
|
||||
ai_reviewer_lanes: Array<{
|
||||
agent_id: string
|
||||
label: string
|
||||
role: string
|
||||
allowed_output: string
|
||||
write_allowed: boolean
|
||||
}>
|
||||
mainstream_tool_lanes: Array<{
|
||||
tool_id: string
|
||||
label: string
|
||||
category: string
|
||||
integration_status: string
|
||||
recommended_role: string
|
||||
blocked_now: string[]
|
||||
}>
|
||||
gate_boundaries: Record<string, boolean>
|
||||
}
|
||||
|
||||
const agents = [
|
||||
{ name: 'Hermes', roleKey: 'hermes', state: 'wired' },
|
||||
{ name: 'OpenClaw', roleKey: 'openclaw', state: 'wired' },
|
||||
@@ -89,9 +167,50 @@ const summaryCards = [
|
||||
{ key: 'report', icon: Gauge, iconClassName: 'text-lime-300' },
|
||||
]
|
||||
|
||||
async function fetchProductCodeReviewGate(): Promise<ProductCodeReviewGateSnapshot | null> {
|
||||
if (!API_BASE) return null
|
||||
const response = await fetch(`${API_BASE}/api/v1/agents/product-code-review-gate`, {
|
||||
cache: 'no-store',
|
||||
})
|
||||
if (!response.ok) return null
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export default function CodeReviewPage({ params }: { params: { locale: string } }) {
|
||||
const t = useTranslations('codeReview')
|
||||
const [gateSnapshot, setGateSnapshot] = useState<ProductCodeReviewGateSnapshot | null>(null)
|
||||
const [gateLoaded, setGateLoaded] = useState(false)
|
||||
const evidenceHref = `/${params.locale}/awooop/runs`
|
||||
const products = useMemo(
|
||||
() => gateSnapshot?.product_review_matrix ?? [],
|
||||
[gateSnapshot]
|
||||
)
|
||||
const preDeployGates = useMemo(
|
||||
() => gateSnapshot?.pre_deploy_gates.slice(0, 4) ?? [],
|
||||
[gateSnapshot]
|
||||
)
|
||||
const postDeployGates = useMemo(
|
||||
() => gateSnapshot?.post_deploy_gates.slice(0, 4) ?? [],
|
||||
[gateSnapshot]
|
||||
)
|
||||
const toolLanes = useMemo(
|
||||
() => gateSnapshot?.mainstream_tool_lanes ?? [],
|
||||
[gateSnapshot]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
fetchProductCodeReviewGate()
|
||||
.then((payload) => {
|
||||
if (!cancelled) setGateSnapshot(payload)
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setGateLoaded(true)
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<AppLayout locale={params.locale}>
|
||||
@@ -117,6 +236,209 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
|
||||
<IwoooSReadOnlyBridge variant="dark" />
|
||||
|
||||
<section
|
||||
id="product-code-review-gate"
|
||||
data-testid="product-code-review-gate"
|
||||
className="grid min-w-0 gap-4 rounded border border-gray-800 bg-gray-950 p-4"
|
||||
>
|
||||
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-start">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 text-xs font-mono uppercase text-amber-300">
|
||||
<ShieldAlert className="h-4 w-4" />
|
||||
{t('gateBoard.eyebrow')}
|
||||
</div>
|
||||
<h2 className="mt-2 text-xl font-semibold text-white">{t('gateBoard.title')}</h2>
|
||||
<p className="mt-2 max-w-3xl text-sm leading-6 text-gray-400">{t('gateBoard.subtitle')}</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs font-mono text-gray-400 sm:grid-cols-4 lg:min-w-[30rem]">
|
||||
<div className="rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
<div className="text-gray-500">{t('gateBoard.metrics.current')}</div>
|
||||
<div className="mt-1 text-lg text-white">{gateSnapshot?.program_status.current_task_id ?? '--'}</div>
|
||||
</div>
|
||||
<div className="rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
<div className="text-gray-500">{t('gateBoard.metrics.next')}</div>
|
||||
<div className="mt-1 text-lg text-white">{gateSnapshot?.program_status.next_task_id ?? '--'}</div>
|
||||
</div>
|
||||
<div className="rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
<div className="text-gray-500">{t('gateBoard.metrics.writeGate')}</div>
|
||||
<div className="mt-1 text-lg text-emerald-200">{gateSnapshot?.rollups.active_write_gate_count ?? 0}</div>
|
||||
</div>
|
||||
<div className="rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
<div className="text-gray-500">{t('gateBoard.metrics.actions')}</div>
|
||||
<div className="mt-1 text-lg text-emerald-200">{gateSnapshot?.rollups.action_button_count ?? 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-6">
|
||||
{[
|
||||
{
|
||||
key: 'products',
|
||||
value: gateSnapshot?.rollups.product_scope_count ?? products.length,
|
||||
icon: Globe2,
|
||||
},
|
||||
{
|
||||
key: 'routes',
|
||||
value: gateSnapshot?.tenant_asset_inventory_summary.public_route_count ?? gateSnapshot?.rollups.public_route_count_minimum ?? 0,
|
||||
icon: GitBranch,
|
||||
},
|
||||
{
|
||||
key: 'pre',
|
||||
value: gateSnapshot?.rollups.pre_deploy_gate_count ?? 0,
|
||||
icon: ShieldCheck,
|
||||
},
|
||||
{
|
||||
key: 'post',
|
||||
value: gateSnapshot?.rollups.post_deploy_gate_count ?? 0,
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
{
|
||||
key: 'reviewers',
|
||||
value: gateSnapshot?.rollups.ai_reviewer_count ?? 0,
|
||||
icon: Bot,
|
||||
},
|
||||
{
|
||||
key: 'tools',
|
||||
value: gateSnapshot?.rollups.mainstream_tool_count ?? 0,
|
||||
icon: PackageSearch,
|
||||
},
|
||||
].map((metric) => {
|
||||
const Icon = metric.icon
|
||||
return (
|
||||
<div key={metric.key} className="rounded border border-gray-800 bg-black/25 p-3">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||||
<Icon className="h-4 w-4 text-sky-300" />
|
||||
{t(`gateBoard.metricCards.${metric.key}.label` as never)}
|
||||
</div>
|
||||
<div className="mt-3 text-2xl font-semibold text-white">{metric.value}</div>
|
||||
<div className="mt-1 text-xs leading-5 text-gray-500">{t(`gateBoard.metricCards.${metric.key}.detail` as never)}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 lg:grid-cols-5">
|
||||
{['assets', 'preDeploy', 'aiReview', 'postDeploy', 'ownerGate'].map((step, index) => (
|
||||
<div key={step} className="min-w-0 rounded border border-gray-800 bg-black/20 px-3 py-3">
|
||||
<div className="font-mono text-xs text-gray-500">{String(index + 1).padStart(2, '0')}</div>
|
||||
<div className="mt-2 text-sm font-semibold text-white">{t(`gateBoard.flow.${step}.title` as never)}</div>
|
||||
<div className="mt-1 text-xs leading-5 text-gray-400">{t(`gateBoard.flow.${step}.detail` as never)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!gateLoaded ? (
|
||||
<div className="rounded border border-gray-800 bg-black/20 px-3 py-2 text-sm text-gray-500">
|
||||
{t('gateBoard.loading')}
|
||||
</div>
|
||||
) : gateSnapshot ? null : (
|
||||
<div className="rounded border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-100">
|
||||
{t('gateBoard.unavailable')}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section
|
||||
data-testid="product-code-review-matrix"
|
||||
className="grid min-w-0 gap-4 rounded border border-gray-800 bg-gray-950 p-4"
|
||||
>
|
||||
<div className="grid gap-2 md:grid-cols-[minmax(0,1fr)_auto] md:items-center">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-white">
|
||||
<Globe2 className="h-4 w-4 text-sky-300" />
|
||||
{t('products.title')}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-gray-500">{t('products.subtitle')}</div>
|
||||
</div>
|
||||
<div className="rounded border border-emerald-500/30 px-3 py-1 text-xs font-mono text-emerald-200">
|
||||
{publicBoundaryText('runtime_execution_authorized=false')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2 md:grid-cols-2 xl:grid-cols-4">
|
||||
{products.map((product) => (
|
||||
<div key={product.product_id} className="min-w-0 rounded border border-gray-800 bg-black/20 p-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="font-mono text-xs text-gray-500">{product.product_id}</div>
|
||||
<div className="mt-1 break-words text-sm font-semibold text-white">{product.product_name}</div>
|
||||
</div>
|
||||
<span className="shrink-0 rounded bg-sky-500/10 px-2 py-1 text-xs font-mono text-sky-200">
|
||||
{product.gate_state === 'owner_review_required'
|
||||
? t('products.states.ownerReview')
|
||||
: product.gate_state === 'source_mapping_required'
|
||||
? t('products.states.sourceMapping')
|
||||
: t('products.states.visible')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-3 grid grid-cols-3 gap-2 text-xs">
|
||||
<div className="rounded border border-gray-800 px-2 py-2">
|
||||
<div className="text-gray-500">{t('products.labels.routes')}</div>
|
||||
<div className="mt-1 font-mono text-white">{product.public_route_count}</div>
|
||||
</div>
|
||||
<div className="rounded border border-gray-800 px-2 py-2">
|
||||
<div className="text-gray-500">{t('products.labels.sources')}</div>
|
||||
<div className="mt-1 font-mono text-white">{product.source_repo_count}</div>
|
||||
</div>
|
||||
<div className="rounded border border-gray-800 px-2 py-2">
|
||||
<div className="text-gray-500">{t('products.labels.gates')}</div>
|
||||
<div className="mt-1 font-mono text-emerald-200">{product.runtime_gate_count}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 break-words text-xs leading-5 text-gray-500">
|
||||
{product.owner_lane} · {product.coverage_status}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 lg:grid-cols-2">
|
||||
<div data-testid="product-code-review-prepost-gates" className="rounded border border-gray-800 bg-gray-950 p-4">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-white">
|
||||
<ShieldCheck className="h-4 w-4 text-emerald-300" />
|
||||
{t('prepost.title')}
|
||||
</div>
|
||||
<div className="mt-3 grid gap-3 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-mono uppercase text-gray-500">{t('prepost.pre')}</div>
|
||||
{preDeployGates.map((gate) => (
|
||||
<div key={gate.gate_id} className="rounded border border-gray-800 bg-black/20 px-3 py-2">
|
||||
<div className="text-sm font-semibold text-white">{gate.label}</div>
|
||||
<div className="mt-1 text-xs text-gray-500">{gate.owner_agent} · {gate.status}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-mono uppercase text-gray-500">{t('prepost.post')}</div>
|
||||
{postDeployGates.map((gate) => (
|
||||
<div key={gate.gate_id} className="rounded border border-gray-800 bg-black/20 px-3 py-2">
|
||||
<div className="text-sm font-semibold text-white">{gate.label}</div>
|
||||
<div className="mt-1 text-xs text-gray-500">{gate.owner_agent} · {gate.status}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-testid="product-code-review-tool-lanes" className="rounded border border-gray-800 bg-gray-950 p-4">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-white">
|
||||
<PackageSearch className="h-4 w-4 text-amber-300" />
|
||||
{t('tools.title')}
|
||||
</div>
|
||||
<div className="mt-3 grid gap-2 sm:grid-cols-2">
|
||||
{toolLanes.map((tool) => (
|
||||
<div key={tool.tool_id} className="rounded border border-gray-800 bg-black/20 px-3 py-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="font-semibold text-white">{tool.label}</div>
|
||||
<div className="font-mono text-xs text-amber-200">{t('tools.candidate')}</div>
|
||||
</div>
|
||||
<div className="mt-1 text-xs leading-5 text-gray-500">{tool.category} · {tool.integration_status}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="code-review-codex-handoff-board"
|
||||
data-testid="code-review-codex-handoff-board"
|
||||
|
||||
370
docs/evaluations/product_code_review_gate_2026-06-19.json
Normal file
370
docs/evaluations/product_code_review_gate_2026-06-19.json
Normal file
@@ -0,0 +1,370 @@
|
||||
{
|
||||
"schema_version": "product_code_review_gate_v1",
|
||||
"generated_at": "2026-06-19T00:42:00+08:00",
|
||||
"program_status": {
|
||||
"overall_completion_percent": 100,
|
||||
"current_priority": "P2",
|
||||
"current_task_id": "P2-111",
|
||||
"next_task_id": "P2-112",
|
||||
"read_only_mode": true,
|
||||
"runtime_authority": "repo_only_product_code_review_gate_no_external_scanner_or_write",
|
||||
"status_note": "P2-111 將全產品資產台帳、Gitea code-review、供應鏈漂移、Aider 事件與 AI reviewer 分工收斂成推版前後防木馬 Gate 讀回;本輪不啟用外部掃描、不改 workflow、不 auto-merge、不部署、不讀 secret。"
|
||||
},
|
||||
"source_refs": [
|
||||
"apps/api/src/services/platform_operator_service.py",
|
||||
".gitea/workflows/code-review.yaml",
|
||||
"scripts/ci_code_review.py",
|
||||
"apps/api/src/services/local_code_review_service.py",
|
||||
"apps/api/src/services/aider_event_service.py",
|
||||
"docs/evaluations/package_supply_chain_inventory_2026-06-04.json",
|
||||
"docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json"
|
||||
],
|
||||
"rollups": {
|
||||
"product_scope_count": 16,
|
||||
"public_route_count_minimum": 31,
|
||||
"source_candidate_repo_count": 10,
|
||||
"pre_deploy_gate_count": 8,
|
||||
"post_deploy_gate_count": 6,
|
||||
"ai_reviewer_count": 5,
|
||||
"mainstream_tool_count": 9,
|
||||
"owner_review_required_count": 9,
|
||||
"critical_gap_count": 6,
|
||||
"blocked_operation_count": 17,
|
||||
"active_write_gate_count": 0,
|
||||
"action_button_count": 0
|
||||
},
|
||||
"pre_deploy_gates": [
|
||||
{
|
||||
"gate_id": "gitea_deterministic_diff_review",
|
||||
"label": "Gitea deterministic diff review",
|
||||
"coverage": "active_current_repo",
|
||||
"status": "wired",
|
||||
"owner_agent": "hermes",
|
||||
"evidence_refs": [".gitea/workflows/code-review.yaml", "scripts/ci_code_review.py"],
|
||||
"current_gap": "目前主要覆蓋 AWOOOI main push,尚未擴成所有產品的必經 pre-deploy Gate。",
|
||||
"next_action": "將所有產品來源範圍映射到同一份 gate packet,阻擋未審查 deploy。"
|
||||
},
|
||||
{
|
||||
"gate_id": "secret_pattern_guard",
|
||||
"label": "Secret / token diff guard",
|
||||
"coverage": "active_current_repo",
|
||||
"status": "wired",
|
||||
"owner_agent": "openclaw",
|
||||
"evidence_refs": ["scripts/ci_code_review.py", "scripts/ci/check-gitea-step-env-secrets.js"],
|
||||
"current_gap": "已做 diff pattern 與 workflow secret surface guard,缺 full-history gitleaks lane。",
|
||||
"next_action": "新增 gitleaks approval packet;未批准前只呈現工具候選,不執行全史掃描。"
|
||||
},
|
||||
{
|
||||
"gate_id": "high_risk_operation_guard",
|
||||
"label": "Destructive operation guard",
|
||||
"coverage": "active_current_repo",
|
||||
"status": "wired",
|
||||
"owner_agent": "elephant_alpha",
|
||||
"evidence_refs": ["scripts/ci_code_review.py", "docs/HARD_RULES.md"],
|
||||
"current_gap": "只抓高風險字串,不足以判斷資料流、權限升級或木馬行為。",
|
||||
"next_action": "接 Semgrep / CodeQL candidate,並由 ElephantAlpha 做高風險人工 review packet。"
|
||||
},
|
||||
{
|
||||
"gate_id": "supply_chain_drift_gate",
|
||||
"label": "Dependency / supply-chain drift",
|
||||
"coverage": "repo_only_snapshot",
|
||||
"status": "wired",
|
||||
"owner_agent": "openclaw",
|
||||
"evidence_refs": ["docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json"],
|
||||
"current_gap": "OSV / Trivy / registry / license lookup 尚未批准,不能宣稱已抓到所有 CVE。",
|
||||
"next_action": "建立 external scanner activation owner packet 與 no-write schedule gate。"
|
||||
},
|
||||
{
|
||||
"gate_id": "container_iac_gate",
|
||||
"label": "Container / IaC review",
|
||||
"coverage": "repo_only_snapshot",
|
||||
"status": "partial",
|
||||
"owner_agent": "nemotron",
|
||||
"evidence_refs": ["docs/evaluations/package_supply_chain_inventory_2026-06-04.json"],
|
||||
"current_gap": "Docker digest、checksum、K8s/IaC policy 仍是 read-only gap。",
|
||||
"next_action": "接 Trivy / Checkov candidate 與 digest pin approval packet。"
|
||||
},
|
||||
{
|
||||
"gate_id": "aider_patch_boundary",
|
||||
"label": "Aider patch boundary",
|
||||
"coverage": "event_intake_present",
|
||||
"status": "candidate_only",
|
||||
"owner_agent": "aider",
|
||||
"evidence_refs": ["apps/api/src/api/v1/aider_events.py", "apps/api/src/services/aider_event_service.py"],
|
||||
"current_gap": "Aider 事件可進 Redis stream / Incident,但尚未接成 code-review 修補批准包。",
|
||||
"next_action": "Aider 只作 approved patch executor,輸出 diff、test receipt、rollback note,不得 auto-merge。"
|
||||
},
|
||||
{
|
||||
"gate_id": "ai_reviewer_consensus",
|
||||
"label": "AI reviewer consensus",
|
||||
"coverage": "agent_contract_defined",
|
||||
"status": "partial",
|
||||
"owner_agent": "openclaw",
|
||||
"evidence_refs": ["apps/api/src/services/local_code_review_service.py", "docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md"],
|
||||
"current_gap": "現有 LLM review 可用於 PR / push 摘要,但沒有多模型交叉 reviewer scorecard。",
|
||||
"next_action": "建立 Hermes / OpenClaw / ElephantAlpha / NemoTron / Aider 分工與 scorecard readback。"
|
||||
},
|
||||
{
|
||||
"gate_id": "human_owner_gate",
|
||||
"label": "Owner review / CODEOWNERS gate",
|
||||
"coverage": "policy_required",
|
||||
"status": "gap",
|
||||
"owner_agent": "elephant_alpha",
|
||||
"evidence_refs": ["docs/HARD_RULES.md", "docs/security/S4-9-REVIEWER-VALIDATION-CHECKLIST.md"],
|
||||
"current_gap": "高風險產品與跨產品修改尚未有統一 CODEOWNERS / owner response 阻擋。",
|
||||
"next_action": "建立 all-products CODEOWNERS / owner lane proposal,不直接修改 workflow。"
|
||||
}
|
||||
],
|
||||
"post_deploy_gates": [
|
||||
{
|
||||
"gate_id": "deploy_marker_readback",
|
||||
"label": "Deploy marker readback",
|
||||
"coverage": "active_current_repo",
|
||||
"status": "wired",
|
||||
"owner_agent": "hermes",
|
||||
"evidence_refs": ["docs/LOGBOOK.md"],
|
||||
"current_gap": "目前主要靠人工回填與 smoke 紀錄,尚未自動串回每個產品 release gate。",
|
||||
"next_action": "把 deploy marker、image revision、route smoke 寫入 product release receipt read model。"
|
||||
},
|
||||
{
|
||||
"gate_id": "production_route_smoke",
|
||||
"label": "Production route smoke",
|
||||
"coverage": "active_current_repo",
|
||||
"status": "wired",
|
||||
"owner_agent": "nemotron",
|
||||
"evidence_refs": [".gitea/workflows/cd.yaml", "docs/LOGBOOK.md"],
|
||||
"current_gap": "AWOOOI routes 有 post-deploy smoke,其他產品尚未統一納入。",
|
||||
"next_action": "以 Tenants public routes 產生 smoke matrix,成功安靜、失敗進 AwoooI SRE 戰情室。"
|
||||
},
|
||||
{
|
||||
"gate_id": "artifact_provenance",
|
||||
"label": "Artifact provenance / signing",
|
||||
"coverage": "candidate",
|
||||
"status": "gap",
|
||||
"owner_agent": "openclaw",
|
||||
"evidence_refs": ["docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json"],
|
||||
"current_gap": "尚未有 SLSA provenance、Sigstore / cosign image signing 與 verification readback。",
|
||||
"next_action": "建立 signing / provenance activation approval packet;未批准前不推 registry 或改 deploy。"
|
||||
},
|
||||
{
|
||||
"gate_id": "rollback_verifier",
|
||||
"label": "Rollback / verifier receipt",
|
||||
"coverage": "policy_required",
|
||||
"status": "partial",
|
||||
"owner_agent": "elephant_alpha",
|
||||
"evidence_refs": ["docs/HARD_RULES.md"],
|
||||
"current_gap": "部分部署有 smoke,但缺統一 rollback owner、verifier plan 與 fail-close 判定。",
|
||||
"next_action": "把 rollback owner、verifier plan、post-check receipt 做成必填 gate。"
|
||||
},
|
||||
{
|
||||
"gate_id": "runtime_anomaly_watch",
|
||||
"label": "Runtime anomaly watch",
|
||||
"coverage": "observability_candidate",
|
||||
"status": "partial",
|
||||
"owner_agent": "hermes",
|
||||
"evidence_refs": ["docs/evaluations/ai_agent_report_no_write_analysis_runtime_2026-06-18.json"],
|
||||
"current_gap": "告警、報表與 code-review 尚未用同一個 release fingerprint 關聯。",
|
||||
"next_action": "把 release fingerprint 關聯到 incident、Sentry、SigNoz、K8s 與 SRE digest。"
|
||||
},
|
||||
{
|
||||
"gate_id": "learning_writeback",
|
||||
"label": "KM / PlayBook writeback",
|
||||
"coverage": "candidate",
|
||||
"status": "gap",
|
||||
"owner_agent": "hermes",
|
||||
"evidence_refs": ["docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md"],
|
||||
"current_gap": "Code Review 結果還沒有完整回寫 KM / PlayBook trust / reviewer scorecard。",
|
||||
"next_action": "建立 code-review finding -> work item -> fix receipt -> KM / PlayBook trust 的 writeback gate。"
|
||||
}
|
||||
],
|
||||
"ai_reviewer_lanes": [
|
||||
{
|
||||
"agent_id": "hermes",
|
||||
"label": "Hermes",
|
||||
"role": "證據整理、產品資產與 KM / 報表沉澱",
|
||||
"allowed_output": "readback packet, KM draft, report digest draft",
|
||||
"write_allowed": false
|
||||
},
|
||||
{
|
||||
"agent_id": "openclaw",
|
||||
"label": "OpenClaw",
|
||||
"role": "風險仲裁、供應鏈 Gate、owner packet 編排",
|
||||
"allowed_output": "risk verdict, owner gate, policy decision packet",
|
||||
"write_allowed": false
|
||||
},
|
||||
{
|
||||
"agent_id": "elephant_alpha",
|
||||
"label": "ElephantAlpha",
|
||||
"role": "高風險與防木馬 read-only reviewer",
|
||||
"allowed_output": "security review finding, high-risk hold, rollback requirement",
|
||||
"write_allowed": false
|
||||
},
|
||||
{
|
||||
"agent_id": "nemotron",
|
||||
"label": "NemoTron",
|
||||
"role": "長任務回放、供應鏈版本與 post-deploy verifier",
|
||||
"allowed_output": "replay scorecard, smoke matrix, drift comparison",
|
||||
"write_allowed": false
|
||||
},
|
||||
{
|
||||
"agent_id": "aider",
|
||||
"label": "Aider",
|
||||
"role": "批准後的 patch pair-programmer,不作審批者",
|
||||
"allowed_output": "draft patch, lint/test receipt, rollback note",
|
||||
"write_allowed": false
|
||||
}
|
||||
],
|
||||
"mainstream_tool_lanes": [
|
||||
{
|
||||
"tool_id": "codeql",
|
||||
"label": "CodeQL",
|
||||
"category": "semantic_sast",
|
||||
"source_url": "https://docs.github.com/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql",
|
||||
"integration_status": "candidate_owner_approval_required",
|
||||
"recommended_role": "跨語言語意分析與高風險資料流審查",
|
||||
"blocked_now": ["enable workflow", "upload alerts"]
|
||||
},
|
||||
{
|
||||
"tool_id": "semgrep",
|
||||
"label": "Semgrep",
|
||||
"category": "sast_policy",
|
||||
"source_url": "https://semgrep.dev/docs/semgrep-code/overview",
|
||||
"integration_status": "candidate_owner_approval_required",
|
||||
"recommended_role": "快速規則化 SAST、框架風險與組織 policy guard",
|
||||
"blocked_now": ["install scanner", "network rule fetch"]
|
||||
},
|
||||
{
|
||||
"tool_id": "gitleaks",
|
||||
"label": "Gitleaks",
|
||||
"category": "secret_scanning",
|
||||
"source_url": "https://gitleaks.io/",
|
||||
"integration_status": "candidate_owner_approval_required",
|
||||
"recommended_role": "repo / diff / history secret scanning",
|
||||
"blocked_now": ["full-history scan", "write report artifact"]
|
||||
},
|
||||
{
|
||||
"tool_id": "osv_scanner",
|
||||
"label": "OSV-Scanner",
|
||||
"category": "dependency_vulnerability",
|
||||
"source_url": "https://google.github.io/osv-scanner/",
|
||||
"integration_status": "candidate_owner_approval_required",
|
||||
"recommended_role": "dependency manifest / lockfile vulnerability matching",
|
||||
"blocked_now": ["external vulnerability lookup"]
|
||||
},
|
||||
{
|
||||
"tool_id": "trivy",
|
||||
"label": "Trivy",
|
||||
"category": "container_iac_vulnerability",
|
||||
"source_url": "https://trivy.dev/",
|
||||
"integration_status": "candidate_owner_approval_required",
|
||||
"recommended_role": "container image、filesystem、IaC 與 secret scan",
|
||||
"blocked_now": ["image pull", "external DB update"]
|
||||
},
|
||||
{
|
||||
"tool_id": "openssf_scorecard",
|
||||
"label": "OpenSSF Scorecard",
|
||||
"category": "repo_security_posture",
|
||||
"source_url": "https://scorecard.dev/",
|
||||
"integration_status": "candidate_owner_approval_required",
|
||||
"recommended_role": "repository security posture / branch / token / CI hygiene score",
|
||||
"blocked_now": ["external repo metadata lookup"]
|
||||
},
|
||||
{
|
||||
"tool_id": "slsa",
|
||||
"label": "SLSA",
|
||||
"category": "provenance_framework",
|
||||
"source_url": "https://slsa.dev/",
|
||||
"integration_status": "candidate_owner_approval_required",
|
||||
"recommended_role": "build provenance 與 artifact integrity framework",
|
||||
"blocked_now": ["provenance emission", "workflow write"]
|
||||
},
|
||||
{
|
||||
"tool_id": "sigstore_cosign",
|
||||
"label": "Sigstore / cosign",
|
||||
"category": "artifact_signing",
|
||||
"source_url": "https://www.sigstore.dev/",
|
||||
"integration_status": "candidate_owner_approval_required",
|
||||
"recommended_role": "container / artifact signing and verification",
|
||||
"blocked_now": ["keyless signing", "registry write"]
|
||||
},
|
||||
{
|
||||
"tool_id": "coderabbit_or_snyk",
|
||||
"label": "CodeRabbit / Snyk",
|
||||
"category": "ai_appsec_platform",
|
||||
"source_url": "https://docs.coderabbit.ai/",
|
||||
"integration_status": "paid_or_external_candidate_owner_approval_required",
|
||||
"recommended_role": "PR AI review、SCA、container / IaC 風險與 developer workflow",
|
||||
"blocked_now": ["paid external service", "repo app install"]
|
||||
}
|
||||
],
|
||||
"decision_matrix": [
|
||||
{
|
||||
"risk_lane": "low_ui_or_docs",
|
||||
"reviewer": "Hermes + deterministic guard",
|
||||
"aider_role": "可在批准後起草 patch",
|
||||
"required_gate": "owner_scope_confirmed",
|
||||
"post_deploy": "route smoke + screenshot"
|
||||
},
|
||||
{
|
||||
"risk_lane": "medium_application_logic",
|
||||
"reviewer": "OpenClaw + ElephantAlpha",
|
||||
"aider_role": "只產 patch 與 test receipt",
|
||||
"required_gate": "manual_approval_and_rollback_note",
|
||||
"post_deploy": "API / UI smoke + incident watch"
|
||||
},
|
||||
{
|
||||
"risk_lane": "high_security_supply_chain",
|
||||
"reviewer": "ElephantAlpha + OpenClaw + NemoTron replay",
|
||||
"aider_role": "預設禁用,除非單項批准",
|
||||
"required_gate": "owner_response + security_acceptance + verifier_plan",
|
||||
"post_deploy": "deploy marker + provenance / verifier receipt"
|
||||
},
|
||||
{
|
||||
"risk_lane": "critical_runtime_or_secret",
|
||||
"reviewer": "ElephantAlpha hold",
|
||||
"aider_role": "禁止",
|
||||
"required_gate": "break_glass_or_formal_change_window",
|
||||
"post_deploy": "rollback owner + audit evidence + SRE digest"
|
||||
}
|
||||
],
|
||||
"gate_boundaries": {
|
||||
"read_only_api_allowed": true,
|
||||
"workflow_write_allowed": false,
|
||||
"external_scanner_activation_allowed": false,
|
||||
"paid_ai_review_allowed": false,
|
||||
"repo_app_install_allowed": false,
|
||||
"auto_merge_allowed": false,
|
||||
"production_deploy_authorized": false,
|
||||
"aider_auto_patch_allowed": false,
|
||||
"elephantalpha_write_allowed": false,
|
||||
"secret_read_allowed": false,
|
||||
"post_deploy_write_allowed": false,
|
||||
"runtime_execution_allowed": false,
|
||||
"telegram_send_allowed": false,
|
||||
"gateway_queue_write_allowed": false,
|
||||
"host_probe_allowed": false,
|
||||
"registry_push_allowed": false,
|
||||
"artifact_signing_allowed": false,
|
||||
"action_buttons_allowed": false
|
||||
},
|
||||
"next_actions": [
|
||||
{
|
||||
"task_id": "P2-112",
|
||||
"priority": "P1",
|
||||
"summary": "把 product_code_review_gate 接到 Gitea workflow pre-deploy policy readback;高風險先 fail-close,低風險仍需 reviewer receipt。",
|
||||
"gate": "workflow_change_owner_approval_required"
|
||||
},
|
||||
{
|
||||
"task_id": "P2-113",
|
||||
"priority": "P1",
|
||||
"summary": "建立 post-deploy release receipt:deploy marker、image revision、product route smoke、rollback owner、verifier receipt。",
|
||||
"gate": "read_only_release_receipt_first"
|
||||
},
|
||||
{
|
||||
"task_id": "P2-114",
|
||||
"priority": "P1",
|
||||
"summary": "設計外部 scanner 啟用批准包:CodeQL、Semgrep、Gitleaks、OSV、Trivy、OpenSSF、SLSA、Sigstore。",
|
||||
"gate": "cost_and_external_lookup_approval_required"
|
||||
}
|
||||
]
|
||||
}
|
||||
175
docs/schemas/product_code_review_gate_v1.schema.json
Normal file
175
docs/schemas/product_code_review_gate_v1.schema.json
Normal file
@@ -0,0 +1,175 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "urn:awoooi:product-code-review-gate-v1",
|
||||
"title": "AWOOOI product code review gate v1",
|
||||
"description": "全產品推版前後 Code Review / 防木馬 Gate 只讀讀回。此 schema 不授權外部 scanner 啟用、workflow 寫入、auto merge、production deploy、Aider 自動 patch、secret 讀取、host probe、registry push、artifact signing、Telegram 實發或 Gateway queue write。",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schema_version",
|
||||
"generated_at",
|
||||
"program_status",
|
||||
"source_refs",
|
||||
"rollups",
|
||||
"pre_deploy_gates",
|
||||
"post_deploy_gates",
|
||||
"ai_reviewer_lanes",
|
||||
"mainstream_tool_lanes",
|
||||
"decision_matrix",
|
||||
"gate_boundaries",
|
||||
"next_actions"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": { "type": "string", "const": "product_code_review_gate_v1" },
|
||||
"generated_at": { "type": "string", "minLength": 1 },
|
||||
"program_status": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"overall_completion_percent",
|
||||
"current_priority",
|
||||
"current_task_id",
|
||||
"next_task_id",
|
||||
"read_only_mode",
|
||||
"runtime_authority",
|
||||
"status_note"
|
||||
],
|
||||
"properties": {
|
||||
"overall_completion_percent": { "type": "integer", "minimum": 0, "maximum": 100 },
|
||||
"current_priority": { "type": "string", "enum": ["P0", "P1", "P2", "P3"] },
|
||||
"current_task_id": { "type": "string", "const": "P2-111" },
|
||||
"next_task_id": { "type": "string", "minLength": 1 },
|
||||
"read_only_mode": { "type": "boolean", "const": true },
|
||||
"runtime_authority": { "type": "string", "minLength": 1 },
|
||||
"status_note": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"source_refs": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"rollups": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"product_scope_count",
|
||||
"public_route_count_minimum",
|
||||
"source_candidate_repo_count",
|
||||
"pre_deploy_gate_count",
|
||||
"post_deploy_gate_count",
|
||||
"ai_reviewer_count",
|
||||
"mainstream_tool_count",
|
||||
"owner_review_required_count",
|
||||
"critical_gap_count",
|
||||
"blocked_operation_count",
|
||||
"active_write_gate_count",
|
||||
"action_button_count"
|
||||
],
|
||||
"additionalProperties": { "type": "integer" }
|
||||
},
|
||||
"pre_deploy_gates": { "$ref": "#/$defs/gateArray" },
|
||||
"post_deploy_gates": { "$ref": "#/$defs/gateArray" },
|
||||
"ai_reviewer_lanes": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["agent_id", "label", "role", "allowed_output", "write_allowed"],
|
||||
"properties": {
|
||||
"agent_id": { "type": "string", "minLength": 1 },
|
||||
"label": { "type": "string", "minLength": 1 },
|
||||
"role": { "type": "string", "minLength": 1 },
|
||||
"allowed_output": { "type": "string", "minLength": 1 },
|
||||
"write_allowed": { "type": "boolean", "const": false }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"mainstream_tool_lanes": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tool_id",
|
||||
"label",
|
||||
"category",
|
||||
"source_url",
|
||||
"integration_status",
|
||||
"recommended_role",
|
||||
"blocked_now"
|
||||
],
|
||||
"properties": {
|
||||
"tool_id": { "type": "string", "minLength": 1 },
|
||||
"label": { "type": "string", "minLength": 1 },
|
||||
"category": { "type": "string", "minLength": 1 },
|
||||
"source_url": { "type": "string", "minLength": 1 },
|
||||
"integration_status": { "type": "string", "minLength": 1 },
|
||||
"recommended_role": { "type": "string", "minLength": 1 },
|
||||
"blocked_now": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"decision_matrix": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["risk_lane", "reviewer", "aider_role", "required_gate", "post_deploy"],
|
||||
"additionalProperties": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"gate_boundaries": {
|
||||
"type": "object",
|
||||
"required": ["read_only_api_allowed"],
|
||||
"additionalProperties": { "type": "boolean" }
|
||||
},
|
||||
"next_actions": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["task_id", "priority", "summary", "gate"],
|
||||
"additionalProperties": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"gateArray": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gate_id",
|
||||
"label",
|
||||
"coverage",
|
||||
"status",
|
||||
"owner_agent",
|
||||
"evidence_refs",
|
||||
"current_gap",
|
||||
"next_action"
|
||||
],
|
||||
"properties": {
|
||||
"gate_id": { "type": "string", "minLength": 1 },
|
||||
"label": { "type": "string", "minLength": 1 },
|
||||
"coverage": { "type": "string", "minLength": 1 },
|
||||
"status": { "type": "string", "minLength": 1 },
|
||||
"owner_agent": { "type": "string", "minLength": 1 },
|
||||
"evidence_refs": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"current_gap": { "type": "string", "minLength": 1 },
|
||||
"next_action": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
Reference in New Issue
Block a user