feat(governance): 顯示任務批准邊界與進度彙總
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user