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