Skip to content

Commit 19da435

Browse files
committed
feat: 完成阶段五 Filter 治理层(4 类过滤器 + 编排链 + DB 写入)
1 parent 45b3774 commit 19da435

2 files changed

Lines changed: 528 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""Filter chain orchestration for the code review agent.
7+
8+
Orchestrates the filter evaluation pipeline (deny → needs_human_review → pass)
9+
and writes interception records to the database and review report.
10+
"""
11+
12+
from __future__ import annotations
13+
14+
from typing import Optional
15+
16+
from .db.storage import StorageABC
17+
from .filters import (
18+
BaseReviewFilter,
19+
FilterAction,
20+
FilterChainResult,
21+
FilterDecision,
22+
HighRiskScriptFilter,
23+
PathSafetyFilter,
24+
NetworkAccessFilter,
25+
BudgetFilter,
26+
)
27+
from .models import FilterIntercept
28+
29+
30+
class ReviewFilterChain:
31+
"""Filter chain that evaluates commands and persists intercepts to DB.
32+
33+
The chain follows strict ordering: DENY → NEEDS_HUMAN_REVIEW → PASS.
34+
If any filter returns DENY, the chain stops immediately.
35+
"""
36+
37+
def __init__(
38+
self,
39+
storage: StorageABC,
40+
task_id: str,
41+
filters: Optional[list[BaseReviewFilter]] = None,
42+
):
43+
self.storage = storage
44+
self.task_id = task_id
45+
self.filters = filters or []
46+
47+
def add_filter(self, filter_: BaseReviewFilter) -> None:
48+
"""Add a filter to the chain."""
49+
self.filters.append(filter_)
50+
51+
def evaluate(self, command: str, cwd: Optional[str] = None) -> FilterChainResult:
52+
"""Evaluate a command against all filters and persist intercepts.
53+
54+
Each non-PASS decision is written to the database as a FilterIntercept
55+
record, which will be included in the final review report.
56+
57+
Args:
58+
command: The shell command to evaluate.
59+
cwd: Current working directory.
60+
61+
Returns:
62+
FilterChainResult with all decisions and the final action.
63+
"""
64+
result = FilterChainResult()
65+
66+
for filter_ in self.filters:
67+
decision = filter_.evaluate(command, cwd)
68+
result.decisions.append(decision)
69+
70+
# Persist non-PASS decisions to DB
71+
if decision.action != FilterAction.PASS:
72+
intercept = FilterIntercept(
73+
task_id=self.task_id,
74+
stage=decision.stage,
75+
rule=decision.rule,
76+
target=decision.target,
77+
reason=decision.reason,
78+
action=decision.action,
79+
)
80+
self.storage.add_filter_intercept(intercept)
81+
82+
# DENY is final — stop the chain
83+
if decision.action == FilterAction.DENY:
84+
result.final_action = FilterAction.DENY
85+
return result
86+
87+
# Check for NEEDS_HUMAN_REVIEW
88+
for decision in result.decisions:
89+
if decision.action == FilterAction.NEEDS_HUMAN_REVIEW:
90+
result.final_action = FilterAction.NEEDS_HUMAN_REVIEW
91+
return result
92+
93+
result.final_action = FilterAction.PASS
94+
return result
95+
96+
def to_dict(self) -> list[dict]:
97+
"""Serialize filter chain configuration to dict."""
98+
return [{"name": f.name, "type": type(f).__name__} for f in self.filters]
99+
100+
101+
def create_review_filter_chain(
102+
storage: StorageABC,
103+
task_id: str,
104+
block_all_network: bool = True,
105+
max_executions: int = 10,
106+
max_total_time_ms: float = 60_000,
107+
) -> ReviewFilterChain:
108+
"""Create a default review filter chain with DB integration.
109+
110+
Args:
111+
storage: Storage backend for persisting intercepts.
112+
task_id: Current review task ID.
113+
block_all_network: If True, all network access is denied.
114+
max_executions: Maximum script executions per review.
115+
max_total_time_ms: Maximum total execution time in ms.
116+
117+
Returns:
118+
Configured ReviewFilterChain instance.
119+
"""
120+
chain = ReviewFilterChain(storage=storage, task_id=task_id)
121+
chain.add_filter(HighRiskScriptFilter())
122+
chain.add_filter(PathSafetyFilter())
123+
chain.add_filter(NetworkAccessFilter(block_all=block_all_network))
124+
chain.add_filter(BudgetFilter(max_executions=max_executions, max_total_time_ms=max_total_time_ms))
125+
return chain

0 commit comments

Comments
 (0)