Skip to content

Commit fdd790d

Browse files
committed
add a policy for time bound approval
1 parent 18a6288 commit fdd790d

4 files changed

Lines changed: 151 additions & 6 deletions

File tree

tools/src/test/java/dev/cel/tools/ai/AgenticPolicyCompilerTest.java

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,26 @@
2121
import dev.cel.common.types.SimpleType;
2222
import dev.cel.common.types.StructTypeReference;
2323
import dev.cel.expr.ai.Agent;
24-
import dev.cel.expr.ai.AgentContext; // New Import
24+
import dev.cel.expr.ai.AgentContext;
2525
import dev.cel.expr.ai.AgentMessage;
2626
import dev.cel.expr.ai.Finding;
2727
import dev.cel.expr.ai.Tool;
2828
import dev.cel.expr.ai.ToolAnnotations;
2929
import dev.cel.expr.ai.ToolCall;
30-
import dev.cel.expr.ai.TrustLevel; // New Import
30+
import dev.cel.expr.ai.TrustLevel;
3131
import dev.cel.parser.CelStandardMacro;
3232
import dev.cel.policy.testing.PolicyTestSuiteHelper;
3333
import dev.cel.policy.testing.PolicyTestSuiteHelper.PolicyTestSuite;
3434
import dev.cel.policy.testing.PolicyTestSuiteHelper.PolicyTestSuite.PolicyTestSection;
3535
import dev.cel.policy.testing.PolicyTestSuiteHelper.PolicyTestSuite.PolicyTestSection.PolicyTestCase;
3636
import dev.cel.runtime.CelEvaluationException;
3737
import dev.cel.runtime.CelFunctionBinding;
38+
import dev.cel.runtime.CelLateFunctionBindings;
3839
import java.io.IOException;
3940
import java.net.URL;
41+
import java.time.Instant;
4042
import java.util.List;
43+
import java.util.stream.Collectors;
4144
import org.junit.Rule;
4245
import org.junit.Test;
4346
import org.junit.runner.RunWith;
@@ -61,10 +64,12 @@ public class AgenticPolicyCompilerTest {
6164

6265
.addVar("agent.input", StructTypeReference.create("cel.expr.ai.AgentMessage"))
6366
.addVar("agent.context", StructTypeReference.create("cel.expr.ai.AgentContext"))
67+
.addVar("_test_history", ListType.create(StructTypeReference.create("cel.expr.ai.AgentMessage")))
68+
.addVar("now", SimpleType.TIMESTAMP)
69+
6470
.addVar("tool.name", SimpleType.STRING)
6571
.addVar("tool.annotations", StructTypeReference.create("cel.expr.ai.ToolAnnotations"))
6672
.addVar("tool.call", StructTypeReference.create("cel.expr.ai.ToolCall"))
67-
6873
.addFunctionDeclarations(
6974
newFunctionDeclaration(
7075
"ai.finding",
@@ -100,6 +105,31 @@ public class AgenticPolicyCompilerTest {
100105
ListType.create(StructTypeReference.create("cel.expr.ai.Finding")),
101106
ListType.create(StructTypeReference.create("cel.expr.ai.Finding"))
102107
)
108+
),
109+
newFunctionDeclaration(
110+
"agent.history",
111+
newGlobalOverload(
112+
"agent_history",
113+
ListType.create(StructTypeReference.create("cel.expr.ai.AgentMessage"))
114+
)
115+
),
116+
newFunctionDeclaration(
117+
"role",
118+
newMemberOverload(
119+
"list_agent_message_role_string",
120+
ListType.create(StructTypeReference.create("cel.expr.ai.AgentMessage")),
121+
ListType.create(StructTypeReference.create("cel.expr.ai.AgentMessage")),
122+
SimpleType.STRING
123+
)
124+
),
125+
newFunctionDeclaration(
126+
"after",
127+
newMemberOverload(
128+
"list_agent_message_after_timestamp",
129+
ListType.create(StructTypeReference.create("cel.expr.ai.AgentMessage")),
130+
ListType.create(StructTypeReference.create("cel.expr.ai.AgentMessage")),
131+
SimpleType.TIMESTAMP
132+
)
103133
)
104134
)
105135
.addFunctionBindings(
@@ -151,14 +181,40 @@ public class AgenticPolicyCompilerTest {
151181
(args) -> {
152182
List<Finding> actualFindings = (List<Finding>) args[0];
153183
List<Finding> expectedFindings = (List<Finding>) args[1];
154-
155184
return expectedFindings.stream().anyMatch(expected ->
156185
actualFindings.stream().anyMatch(actual ->
157186
actual.getValue().equals(expected.getValue()) &&
158187
actual.getConfidence() >= expected.getConfidence()
159188
)
160189
);
161190
}
191+
),
192+
CelFunctionBinding.from(
193+
"list_agent_message_role_string",
194+
ImmutableList.of(List.class, String.class),
195+
(args) -> {
196+
List<AgentMessage> history = (List<AgentMessage>) args[0];
197+
String role = (String) args[1];
198+
return history.stream()
199+
.filter(m -> m.getRole().equals(role))
200+
.collect(Collectors.toList());
201+
}
202+
),
203+
CelFunctionBinding.from(
204+
"list_agent_message_after_timestamp",
205+
ImmutableList.of(List.class, Instant.class),
206+
(args) -> {
207+
List<AgentMessage> history = (List<AgentMessage>) args[0];
208+
Instant cutoff = (Instant) args[1];
209+
210+
return history.stream()
211+
.filter(m -> {
212+
com.google.protobuf.Timestamp protoTs = m.getTime();
213+
Instant msgTime = Instant.ofEpochSecond(protoTs.getSeconds(), protoTs.getNanos());
214+
return msgTime.compareTo(cutoff) >= 0;
215+
})
216+
.collect(Collectors.toList());
217+
}
162218
)
163219
)
164220
.build();
@@ -188,6 +244,10 @@ private enum AgenticPolicyTestCase {
188244
TRUST_CASCADING(
189245
"trust_cascading.celpolicy",
190246
"trust_cascading_tests.yaml"
247+
),
248+
TIME_BOUND_APPROVAL(
249+
"time_bound_approval.celpolicy",
250+
"time_bound_approval_tests.yaml"
191251
);
192252

193253
private final String policyFilePath;
@@ -217,7 +277,21 @@ private void runTests(Cel cel, CelAbstractSyntaxTree ast, PolicyTestSuite testSu
217277
"%s: %s", testSection.getName(), testCase.getName());
218278
try {
219279
ImmutableMap<String, Object> inputMap = testCase.toInputMap(cel);
220-
Object evalResult = cel.createProgram(ast).eval(inputMap);
280+
281+
List<AgentMessage> history =
282+
inputMap.containsKey("_test_history")
283+
? (List<AgentMessage>) inputMap.get("_test_history")
284+
: ImmutableList.of();
285+
286+
CelLateFunctionBindings bindings = CelLateFunctionBindings.from(
287+
CelFunctionBinding.from(
288+
"agent_history",
289+
ImmutableList.of(), // No args
290+
(args) -> history
291+
)
292+
);
293+
294+
Object evalResult = cel.createProgram(ast).eval(inputMap, bindings);
221295
Object expectedOutput = cel.createProgram(cel.compile(testCase.getOutput()).getAst()).eval();
222296
expect.withMessage(testName).that(evalResult).isEqualTo(expectedOutput);
223297
} catch (CelValidationException e) {
@@ -228,4 +302,4 @@ private void runTests(Cel cel, CelAbstractSyntaxTree ast, PolicyTestSuite testSu
228302
}
229303
}
230304
}
231-
}
305+
}

tools/src/test/java/dev/cel/tools/ai/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ java_library(
2222
"//policy/testing:policy_test_suite_helper",
2323
"//runtime:evaluation_exception",
2424
"//runtime:function_binding",
25+
"//runtime:late_function_binding",
2526
"//tools/ai:agentic_policy_compiler",
2627
"//tools/src/main/java/dev/cel/tools/ai:agent_context_java_proto",
2728
"@maven//:com_google_guava_guava",
2829
"@maven//:com_google_protobuf_protobuf_java",
30+
"@maven//:com_google_protobuf_protobuf_java_util",
2931
"@maven//:com_google_testparameterinjector_test_parameter_injector",
3032
"@maven//:junit_junit",
3133
],
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: "policy.safety.time_bound_approval"
2+
default: allow
3+
4+
variables:
5+
# Define the validity window (30 seconds ago)
6+
- approval_cutoff: now - duration('30s')
7+
8+
# Find approval messages in the valid window
9+
- valid_approvals: >
10+
agent.history()
11+
.after(variables.approval_cutoff)
12+
.role('model')
13+
.filter(m, has(m.metadata.step) && m.metadata.step == 'approval_granted')
14+
15+
- has_valid_approval: variables.valid_approvals.size() > 0
16+
17+
rules:
18+
- description: "Require approval within the last 30 seconds for sensitive writes"
19+
condition: >
20+
tool.name == 'database_write' &&
21+
!variables.has_valid_approval
22+
effect: deny
23+
message: "Authorization expired. Please re-approve the database write operation."
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
description: "Time-Bound Approval Policy Tests"
2+
3+
section:
4+
- name: "Time Window Enforcement"
5+
tests:
6+
- name: "Approval Expired (Deny)"
7+
input:
8+
tool.name:
9+
value: "database_write"
10+
now:
11+
expr: timestamp("2024-01-01T12:01:00Z")
12+
_test_history:
13+
expr: >
14+
[
15+
AgentMessage{
16+
role: "model",
17+
time: timestamp("2024-01-01T12:00:00Z"),
18+
metadata: { "step": "approval_granted" }
19+
}
20+
]
21+
output: >
22+
{
23+
"effect": "deny",
24+
"message": "Authorization expired. Please re-approve the database write operation."
25+
}
26+
27+
- name: "Approval Valid (Allow)"
28+
input:
29+
tool.name:
30+
value: "database_write"
31+
now:
32+
expr: timestamp("2024-01-01T12:00:10Z")
33+
_test_history:
34+
expr: >
35+
[
36+
AgentMessage{
37+
role: "model",
38+
time: timestamp("2024-01-01T12:00:00Z"),
39+
metadata: { "step": "approval_granted" }
40+
}
41+
]
42+
output: >
43+
{
44+
"effect": "allow",
45+
"message": ""
46+
}

0 commit comments

Comments
 (0)