feat(governance): 顯示任務批准邊界與進度彙總
All checks were successful
CD Pipeline / tests (push) Successful in 1m31s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / build-and-deploy (push) Successful in 5m12s
CD Pipeline / post-deploy-checks (push) Successful in 2m21s

This commit is contained in:
Your Name
2026-06-05 09:23:41 +08:00
parent b133f76af5
commit 4f0787f869
17 changed files with 2368 additions and 105 deletions

View File

@@ -26,6 +26,8 @@ def test_load_latest_backlog_snapshot_reads_newest_file(tmp_path):
assert loaded["generated_at"] == "2026-06-04T00:00:00+08:00"
assert loaded["program_status"]["overall_completion_percent"] == 76
assert loaded["rollups"]["total_items"] == 1
assert loaded["progress_summary"]["overall_percent"] == 0
assert loaded["item_approval_boundary_rollup"]["total_items"] == 1
assert loaded["approval_boundaries"]["sdk_installation_allowed"] is False
@@ -65,6 +67,30 @@ def test_load_backlog_snapshot_requires_total_rollup_consistency(tmp_path):
load_latest_ai_agent_automation_backlog_snapshot(tmp_path)
def test_load_backlog_snapshot_requires_item_approval_boundaries(tmp_path):
snapshot = _snapshot()
del snapshot["backlog_items"][0]["approval_boundary"]
(tmp_path / "ai_agent_automation_backlog_2026-06-04.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="approval_boundary"):
load_latest_ai_agent_automation_backlog_snapshot(tmp_path)
def test_load_backlog_snapshot_requires_progress_summary_consistency(tmp_path):
snapshot = _snapshot()
snapshot["progress_summary"]["overall_percent"] = 99
(tmp_path / "ai_agent_automation_backlog_2026-06-04.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="overall_percent"):
load_latest_ai_agent_automation_backlog_snapshot(tmp_path)
def test_load_backlog_snapshot_fails_when_missing(tmp_path):
with pytest.raises(FileNotFoundError):
load_latest_ai_agent_automation_backlog_snapshot(tmp_path)
@@ -93,6 +119,31 @@ def _snapshot(
"by_gate_status": {"read_only_allowed": 1},
"by_owner_agent": {"hermes": 1},
},
"progress_summary": {
"overall_percent": 0,
"done_items": 0,
"planned_items": 1,
"total_items": 1,
"formula": "round(done_items / total_items * 100)status=done 才計入完成。",
"by_priority": [
{
"priority": "P1",
"completion_percent": 0,
"done_items": 0,
"total_items": 1,
}
],
"by_workstream": [
{
"workstream_id": "WS2",
"display_name": "自動化待辦",
"completion_percent": 0,
"done_items": 0,
"total_items": 1,
"next_task_id": "P1-303",
}
],
},
"backlog_items": [
{
"item_id": "AUTO-P1-303",
@@ -109,9 +160,22 @@ def _snapshot(
"risk_level": "medium",
"evidence_refs": ["docs/schemas/ai_agent_automation_backlog_v1.schema.json"],
"acceptance_criteria": ["API 只讀"],
"approval_boundary": {
"mode": "read_only_allowed",
"display_summary": "只讀呈現 committed snapshot不授權任何寫入。",
"allowed_actions": ["讀取 committed snapshot", "顯示治理 UI"],
"blocked_actions": ["production_write", "paid_api_call", "destructive_operation"],
"requires_operator_approval_for": ["任何非只讀操作"],
},
"next_review": "P1-303",
}
],
"item_approval_boundary_rollup": {
"total_items": 1,
"by_mode": {"read_only_allowed": 1},
"items_requiring_explicit_approval": [],
"items_with_blocked_operations": ["AUTO-P1-303"],
},
"approval_boundaries": {
"sdk_installation_allowed": False,
"paid_api_call_allowed": False,

View File

@@ -16,14 +16,23 @@ def test_ai_agent_automation_backlog_snapshot_endpoint_returns_committed_snapsho
assert response.status_code == 200
data = response.json()
assert data["schema_version"] == "ai_agent_automation_backlog_v1"
assert data["program_status"]["overall_completion_percent"] == 100
assert data["program_status"]["overall_completion_percent"] == 70
assert data["program_status"]["read_only_mode"] is True
assert data["program_status"]["current_task_id"] == "P1-106"
assert data["program_status"]["next_task_id"] == "P1-305"
assert data["rollups"]["total_items"] == len(data["backlog_items"]) == 21
assert data["rollups"]["by_priority"]["P1"] == 19
assert data["rollups"]["by_status"]["done"] == 14
assert data["rollups"]["by_gate_status"]["read_only_allowed"] == 18
assert data["program_status"]["current_task_id"] == "P1-306"
assert data["program_status"]["next_task_id"] == "P1-001"
assert data["rollups"]["total_items"] == len(data["backlog_items"]) == 23
assert data["rollups"]["by_priority"]["P1"] == 21
assert data["rollups"]["by_status"]["done"] == 16
assert data["rollups"]["by_gate_status"]["read_only_allowed"] == 20
assert data["progress_summary"]["overall_percent"] == 70
assert data["progress_summary"]["done_items"] == 16
assert data["progress_summary"]["total_items"] == 23
assert data["item_approval_boundary_rollup"]["total_items"] == 23
assert data["item_approval_boundary_rollup"]["items_requiring_explicit_approval"] == [
"AUTO-P1-004",
"AUTO-P2-004",
"AUTO-P3-001",
]
assert data["approval_boundaries"]["sdk_installation_allowed"] is False
assert data["approval_boundaries"]["paid_api_call_allowed"] is False
assert data["approval_boundaries"]["production_routing_allowed"] is False
@@ -34,4 +43,8 @@ def test_ai_agent_automation_backlog_snapshot_endpoint_returns_committed_snapsho
assert any(item["item_id"] == "AUTO-P1-104" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-105" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-106" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-305" for item in data["backlog_items"])
p1_306 = next(item for item in data["backlog_items"] if item["item_id"] == "AUTO-P1-306")
assert p1_306["approval_boundary"]["mode"] == "read_only_allowed"
assert "runtime_execution" in p1_306["approval_boundary"]["blocked_actions"]
assert any(item["item_id"] == "AUTO-P3-001" for item in data["backlog_items"])

View File

@@ -26,6 +26,7 @@ def test_load_latest_inventory_snapshot_reads_newest_file(tmp_path):
assert loaded["generated_at"] == "2026-06-04T00:00:00+08:00"
assert loaded["program_status"]["overall_completion_percent"] == 53
assert loaded["approval_boundaries"]["paid_api_call_allowed"] is False
assert loaded["task_approval_boundary_rollup"]["total_tasks"] == 1
def test_load_inventory_snapshot_requires_read_only_mode(tmp_path):
@@ -52,6 +53,30 @@ def test_load_inventory_snapshot_requires_blocked_approval_boundaries(tmp_path):
load_latest_ai_agent_automation_inventory_snapshot(tmp_path)
def test_load_inventory_snapshot_requires_task_approval_boundaries(tmp_path):
snapshot = _snapshot()
del snapshot["tasks"][0]["approval_boundary"]
(tmp_path / "ai_agent_automation_inventory_snapshot_2026-06-04.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="approval_boundary"):
load_latest_ai_agent_automation_inventory_snapshot(tmp_path)
def test_load_inventory_snapshot_requires_task_boundary_rollup_consistency(tmp_path):
snapshot = _snapshot()
snapshot["task_approval_boundary_rollup"]["by_mode"]["approval_required"] = 1
(tmp_path / "ai_agent_automation_inventory_snapshot_2026-06-04.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="by_mode"):
load_latest_ai_agent_automation_inventory_snapshot(tmp_path)
def test_load_inventory_snapshot_fails_when_missing(tmp_path):
with pytest.raises(FileNotFoundError):
load_latest_ai_agent_automation_inventory_snapshot(tmp_path)
@@ -126,9 +151,22 @@ def _snapshot(
"title": "建立靜態盤點種子",
"output": "seed",
"gate_status": "read_only_allowed",
"approval_boundary": {
"mode": "read_only_allowed",
"display_summary": "只讀呈現 committed snapshot不授權任何寫入。",
"allowed_actions": ["讀取 committed snapshot", "顯示治理 UI"],
"blocked_actions": ["production_write", "paid_api_call", "destructive_operation"],
"requires_operator_approval_for": ["任何非只讀操作"],
},
"next_action": "建立只讀 API。",
}
],
"task_approval_boundary_rollup": {
"total_tasks": 1,
"by_mode": {"read_only_allowed": 1},
"tasks_requiring_explicit_approval": [],
"tasks_with_blocked_operations": ["P0-005"],
},
"evidence": [
{
"evidence_id": "seed",

View File

@@ -18,8 +18,14 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
assert data["schema_version"] == "ai_agent_automation_inventory_snapshot_v1"
assert data["program_status"]["overall_completion_percent"] == 100
assert data["program_status"]["read_only_mode"] is True
assert data["program_status"]["current_task_id"] == "P1-106"
assert data["program_status"]["next_task_id"] == "P1-305"
assert data["program_status"]["current_task_id"] == "P1-306"
assert data["program_status"]["next_task_id"] == "P1-001"
assert data["task_approval_boundary_rollup"]["total_tasks"] == len(data["tasks"]) == 26
assert data["task_approval_boundary_rollup"]["by_mode"]["read_only_allowed"] == 24
assert data["task_approval_boundary_rollup"]["tasks_requiring_explicit_approval"] == [
"P0-001",
"P0-004",
]
assert data["approval_boundaries"]["sdk_installation_allowed"] is False
assert data["approval_boundaries"]["paid_api_call_allowed"] is False
assert data["approval_boundaries"]["production_routing_allowed"] is False
@@ -31,6 +37,12 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
assert any(task["task_id"] == "P1-104" for task in data["tasks"])
assert any(task["task_id"] == "P1-105" for task in data["tasks"])
assert any(task["task_id"] == "P1-106" for task in data["tasks"])
p1_305 = next(task for task in data["tasks"] if task["task_id"] == "P1-305")
assert p1_305["approval_boundary"]["mode"] == "read_only_allowed"
assert "production_write" in p1_305["approval_boundary"]["blocked_actions"]
p1_306 = next(task for task in data["tasks"] if task["task_id"] == "P1-306")
assert p1_306["approval_boundary"]["mode"] == "read_only_allowed"
assert "runtime_execution" in p1_306["approval_boundary"]["blocked_actions"]
assert any(evidence["evidence_id"] == "dependency_risk_policy_api" for evidence in data["evidence"])
assert any(evidence["evidence_id"] == "dependency_drift_check_plan_api" for evidence in data["evidence"])
assert any(
@@ -44,3 +56,5 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
for evidence in data["evidence"]
)
assert any(evidence["evidence_id"] == "offsite_escrow_readiness_status_api" for evidence in data["evidence"])
assert any(evidence["evidence_id"] == "task_approval_boundary_ui" for evidence in data["evidence"])
assert any(evidence["evidence_id"] == "backlog_progress_summary_ui" for evidence in data["evidence"])