veritool 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bridge/__init__.py ADDED
@@ -0,0 +1,41 @@
1
+ from bridge.policy_spec import PolicySpec, NatType, StringType, FinsetType, FunctionDef, BridgeError
2
+ from bridge.z3_encoder import check_policy, compile_policy
3
+
4
+ TAHOE_SPEC = PolicySpec(
5
+ name="tahoe",
6
+ _policy_type="tahoe",
7
+ params={"model": StringType, "price": NatType},
8
+ functions=[
9
+ FunctionDef("floor_price", StringType, NatType,
10
+ mapping={"Tahoe": 45000, "Malibu": 25000}, default=0),
11
+ ],
12
+ violation_expr="price < floor_price(model)",
13
+ description="Tahoe/Malibu minimum price policy",
14
+ )
15
+
16
+ DELETION_SPEC = PolicySpec(
17
+ name="deletion",
18
+ _policy_type="deletion",
19
+ params={"target": StringType},
20
+ violation_expr="Not(in_scope(target))",
21
+ _allowed_scope=["/project/temp", "/project/output"],
22
+ description="File deletion frame policy requiring target in allowed scope",
23
+ )
24
+
25
+
26
+ def bridge_check(policy_name: str, params: dict | None = None, timeout_ms: int = 5000) -> dict:
27
+ spec_map = {
28
+ "tahoe": TAHOE_SPEC,
29
+ "deletion": DELETION_SPEC,
30
+ }
31
+ spec = spec_map.get(policy_name)
32
+ if spec is None:
33
+ return {"status": "error", "reason": f"Unknown policy: {policy_name}"}
34
+ return check_policy(spec, params=params, timeout_ms=timeout_ms)
35
+
36
+
37
+ __all__ = [
38
+ "PolicySpec", "NatType", "StringType", "FinsetType", "FunctionDef", "BridgeError",
39
+ "check_policy", "compile_policy",
40
+ "TAHOE_SPEC", "DELETION_SPEC", "bridge_check",
41
+ ]
bridge/policy_spec.py ADDED
@@ -0,0 +1,43 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+
5
+ class BridgeError(Exception):
6
+ pass
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class TypeSpec:
11
+ name: str
12
+
13
+
14
+ NatType = TypeSpec("Nat")
15
+ StringType = TypeSpec("String")
16
+ BoolType = TypeSpec("Bool")
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class FinsetType:
21
+ elem_type: TypeSpec
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class FunctionDef:
26
+ name: str
27
+ arg_type: TypeSpec
28
+ return_type: TypeSpec
29
+ mapping: dict[str, Any] = field(default_factory=dict)
30
+ default: Any = 0
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class PolicySpec:
35
+ name: str
36
+ params: dict[str, TypeSpec] = field(default_factory=dict)
37
+ functions: list[FunctionDef] = field(default_factory=list)
38
+ violation_expr: str = ""
39
+ description: str = ""
40
+ _tool_name: str = ""
41
+ _allowed_scope: list[str] = field(default_factory=list)
42
+ _param_name: str = "value"
43
+ _policy_type: str = "generic"
bridge/z3_encoder.py ADDED
@@ -0,0 +1,205 @@
1
+ import json
2
+
3
+ from z3 import (
4
+ Solver, Int, Bool, String, StringVal,
5
+ Function, IntSort, BoolSort, StringSort,
6
+ ForAll, Const, Implies, And, Or,
7
+ sat, unknown,
8
+ )
9
+
10
+ from bridge.policy_spec import (
11
+ PolicySpec, FunctionDef, FinsetType,
12
+ NatType, StringType, BoolType,
13
+ BridgeError,
14
+ )
15
+
16
+
17
+ _SORT_MAP = {
18
+ NatType: IntSort(),
19
+ StringType: StringSort(),
20
+ BoolType: BoolSort(),
21
+ }
22
+
23
+
24
+ def _z3_sort(t):
25
+ if isinstance(t, FinsetType):
26
+ return _SORT_MAP.get(t.elem_type)
27
+ return _SORT_MAP.get(t)
28
+
29
+
30
+ def compile_policy(spec: PolicySpec, timeout_ms: int = 5000) -> Solver:
31
+ s = Solver()
32
+ s.set("timeout", timeout_ms)
33
+
34
+ ptype = spec._policy_type if spec._policy_type != "generic" else spec.name
35
+
36
+ if ptype in ("price_floor", "tahoe"):
37
+ _add_floor_price_defs(s, spec)
38
+ elif ptype in ("file_access", "deletion"):
39
+ allowed = getattr(spec, "_allowed_scope", ["/project/temp", "/project/output"])
40
+ _add_in_scope_defs(s, allowed)
41
+ elif ptype == "sql_safety":
42
+ _add_allowed_patterns(s, spec)
43
+ elif ptype == "rate_limit":
44
+ _add_rate_limit_defs(s, spec)
45
+ elif ptype == "role_hours":
46
+ _add_role_hours_defs(s, spec)
47
+ elif ptype == "api_access":
48
+ _add_api_access_defs(s, spec)
49
+ else:
50
+ raise BridgeError(f"Unknown policy type: {ptype}")
51
+ return s
52
+
53
+
54
+ def _add_floor_price_defs(solver, spec: PolicySpec):
55
+ if not spec.functions:
56
+ return
57
+ fn = spec.functions[0]
58
+ sort = _z3_sort(fn.arg_type)
59
+ ret_sort = _z3_sort(fn.return_type)
60
+ if sort is None or ret_sort is None:
61
+ raise BridgeError(f"Unsupported type in function {fn.name}")
62
+
63
+ floor_z3 = Function(fn.name, sort, ret_sort)
64
+ for k, v in fn.mapping.items():
65
+ solver.add(floor_z3(StringVal(k)) == v)
66
+ solver.add(floor_z3(StringVal("")) == fn.default)
67
+ return floor_z3
68
+
69
+
70
+ def _add_in_scope_defs(solver, allowed_paths: list[str]):
71
+ in_scope = Function("in_scope", StringSort(), BoolSort())
72
+ for p in allowed_paths:
73
+ solver.add(in_scope(StringVal(p)) == True)
74
+ return in_scope
75
+
76
+
77
+ def _add_allowed_patterns(solver, spec: PolicySpec):
78
+ if not spec.functions:
79
+ return
80
+ fn_def = spec.functions[0]
81
+ allowed_fn = Function(fn_def.name, StringSort(), BoolSort())
82
+ for q, val in fn_def.mapping.items():
83
+ solver.add(allowed_fn(StringVal(q)) == (val if isinstance(val, bool) else val == True))
84
+ _add_default_false(solver, allowed_fn, fn_def)
85
+ return allowed_fn
86
+
87
+
88
+ def _add_rate_limit_defs(solver, spec: PolicySpec):
89
+ if not spec.functions:
90
+ return
91
+ fn = spec.functions[0]
92
+ max_fn = Function(fn.name, StringSort(), IntSort())
93
+ for k, v in fn.mapping.items():
94
+ solver.add(max_fn(StringVal(k)) == v)
95
+ solver.add(max_fn(StringVal("")) == fn.default)
96
+ return max_fn
97
+
98
+
99
+ def _add_default_false(solver, z3_fn, fn_def):
100
+ x = Const(f"_{z3_fn.name()}_x", StringSort())
101
+ conditions = [x != StringVal(k) for k in fn_def.mapping]
102
+ if conditions:
103
+ solver.add(ForAll([x], Implies(And(*conditions), z3_fn(x) == False)))
104
+
105
+
106
+ def _add_role_hours_defs(solver, spec: PolicySpec):
107
+ if not spec.functions:
108
+ return
109
+ fn_def = spec.functions[0]
110
+ blocked_fn = Function(fn_def.name, StringSort(), BoolSort())
111
+ for action, val in fn_def.mapping.items():
112
+ solver.add(blocked_fn(StringVal(action)) == (val if isinstance(val, bool) else True))
113
+ _add_default_false(solver, blocked_fn, fn_def)
114
+ return blocked_fn
115
+
116
+
117
+ def _add_api_access_defs(solver, spec: PolicySpec):
118
+ if len(spec.functions) >= 1:
119
+ fn_def = spec.functions[0]
120
+ ep_fn = Function(fn_def.name, StringSort(), BoolSort())
121
+ for ep, val in fn_def.mapping.items():
122
+ solver.add(ep_fn(StringVal(ep)) == (val if isinstance(val, bool) else True))
123
+ _add_default_false(solver, ep_fn, fn_def)
124
+ if len(spec.functions) >= 2:
125
+ fn_def = spec.functions[1]
126
+ method_fn = Function(fn_def.name, StringSort(), BoolSort())
127
+ for m, val in fn_def.mapping.items():
128
+ solver.add(method_fn(StringVal(m)) == (val if isinstance(val, bool) else True))
129
+ _add_default_false(solver, method_fn, fn_def)
130
+
131
+
132
+ def check_policy(spec: PolicySpec, params: dict | None = None, timeout_ms: int = 5000) -> dict:
133
+ solver = compile_policy(spec, timeout_ms)
134
+ ptype = spec._policy_type if spec._policy_type != "generic" else spec.name
135
+
136
+ if params:
137
+ if ptype in ("price_floor", "tahoe"):
138
+ model_val = params.get("model", "")
139
+ price_val = params.get("price", 0)
140
+ solver.add(String("model") == StringVal(model_val))
141
+ solver.add(Int("price") == price_val)
142
+ floor_z3 = Function("floor_price", StringSort(), IntSort())
143
+ solver.add(Int("price") < floor_z3(String("model")))
144
+
145
+ elif ptype in ("file_access", "deletion"):
146
+ target_val = params.get("target", "")
147
+ solver.add(String("target") == StringVal(target_val))
148
+ in_scope = Function("in_scope", StringSort(), BoolSort())
149
+ solver.add(in_scope(String("target")) == False)
150
+
151
+ elif ptype == "sql_safety":
152
+ query_val = params.get("query", "")
153
+ solver.add(String("query") == StringVal(query_val))
154
+ allowed_fn = Function("allowed_query_pattern", StringSort(), BoolSort())
155
+ solver.add(allowed_fn(String("query")) == False)
156
+
157
+ elif ptype == "rate_limit":
158
+ api_key_val = params.get("api_key", "")
159
+ current_count_val = params.get("current_count", 0)
160
+ solver.add(String("api_key") == StringVal(api_key_val))
161
+ solver.add(Int("current_count") == current_count_val)
162
+ max_fn = Function("max_per_minute", StringSort(), IntSort())
163
+ solver.add(Int("current_count") >= max_fn(String("api_key")))
164
+
165
+ elif ptype == "role_hours":
166
+ role_val = params.get("role", "")
167
+ hour_val = params.get("hour", 0)
168
+ action_val = params.get("action", "")
169
+ solver.add(String("role") == StringVal(role_val))
170
+ solver.add(Int("hour") == hour_val)
171
+ solver.add(String("action") == StringVal(action_val))
172
+ blocked_fn = Function("admin_blocked_actions", StringSort(), BoolSort())
173
+ solver.add(String("role") == StringVal("admin"))
174
+ solver.add(Int("hour") > 22)
175
+ solver.add(blocked_fn(String("action")) == True)
176
+
177
+ elif ptype == "api_access":
178
+ endpoint_val = params.get("endpoint", "")
179
+ method_val = params.get("method", "")
180
+ solver.add(String("endpoint") == StringVal(endpoint_val))
181
+ solver.add(String("method") == StringVal(method_val))
182
+ ep_fn = Function("allowed_endpoint", StringSort(), BoolSort())
183
+ method_fn = Function("allowed_method", StringSort(), BoolSort())
184
+ solver.add(Or(ep_fn(String("endpoint")) == False,
185
+ method_fn(String("method")) == False))
186
+
187
+ result = solver.check()
188
+ if result == sat:
189
+ m = solver.model()
190
+ witness = {}
191
+ for d in m.decls():
192
+ val = m[d]
193
+ if val is not None:
194
+ try:
195
+ witness[d.name()] = val.as_long()
196
+ except Exception:
197
+ try:
198
+ witness[d.name()] = str(val)
199
+ except Exception:
200
+ witness[d.name()] = repr(val)
201
+ return {"status": "violation", "witness": witness}
202
+ elif result == unknown:
203
+ return {"status": "unknown", "reason": "Z3 timed out or incomplete"}
204
+ else:
205
+ return {"status": "permitted"}
cli/__init__.py ADDED
@@ -0,0 +1,184 @@
1
+ import argparse
2
+ import json
3
+ import sys
4
+
5
+ from cli.auto_generator import AutoGenerator
6
+ from cli.round_trip import round_trip_verify
7
+
8
+
9
+ def main():
10
+ parser = argparse.ArgumentParser(
11
+ prog="veritool",
12
+ description="VeriTool — Formal verification framework for LLM tool-calling",
13
+ )
14
+ sub = parser.add_subparsers(dest="command")
15
+
16
+ p_run = sub.add_parser("run", help="Start verifier with specified policies")
17
+ p_run.add_argument("policies", nargs="+", help="Policy names or YAML files")
18
+
19
+ p_check = sub.add_parser("check", help="One-shot check a tool call")
20
+ p_check.add_argument("tool_call", help="JSON string or file path")
21
+
22
+ p_create = sub.add_parser("create", help="Generate policy from natural language")
23
+ p_create.add_argument("description", help="Policy description in plain English")
24
+
25
+ p_test = sub.add_parser("test", help="Run policy test suite")
26
+ p_test.add_argument("policy", nargs="?", help="Policy name (omit for all)")
27
+
28
+ p_status = sub.add_parser("status", help="Show runtime status and metrics")
29
+ p_hot = sub.add_parser("hot-reload", help="Deploy policy without restart")
30
+ p_hot.add_argument("policy_file", help="Path to policy YAML file")
31
+ p_rollback = sub.add_parser("rollback", help="Revert to previous policy version")
32
+ p_rollback.add_argument("version", help="Version to rollback to")
33
+
34
+ p_dashboard = sub.add_parser("dashboard", help="Launch monitoring dashboard")
35
+ p_dashboard.add_argument("--port", type=int, default=8501)
36
+
37
+ p_verify = sub.add_parser("verify", help="CI/CD — run all checks before deploy")
38
+ p_wrap = sub.add_parser("wrap", help="Auto-wrap a supported framework")
39
+ p_wrap.add_argument("framework", choices=["langchain", "crewai", "autogen"])
40
+
41
+ args = parser.parse_args()
42
+
43
+ if args.command == "create":
44
+ _cmd_create(args.description)
45
+ elif args.command == "check":
46
+ _cmd_check(args.tool_call)
47
+ elif args.command == "test":
48
+ _cmd_test(args.policy)
49
+ elif args.command == "run":
50
+ _cmd_run(args.policies)
51
+ elif args.command == "status":
52
+ _cmd_status()
53
+ elif args.command == "hot-reload":
54
+ _cmd_hot_reload(args.policy_file)
55
+ elif args.command == "rollback":
56
+ _cmd_rollback(args.version)
57
+ elif args.command == "dashboard":
58
+ _cmd_dashboard(args.port)
59
+ elif args.command == "verify":
60
+ _cmd_verify()
61
+ elif args.command == "wrap":
62
+ _cmd_wrap(args.framework)
63
+ else:
64
+ parser.print_help()
65
+ sys.exit(1)
66
+
67
+
68
+ def _cmd_create(description: str):
69
+ gen = AutoGenerator()
70
+ result = gen.generate(description)
71
+ if result["status"] == "ok":
72
+ print(f" Policy '{result['policy_name']}' created")
73
+ for artifact in result["artifacts"]:
74
+ print(f" ✓ Generated {artifact}")
75
+ rtv = round_trip_verify(result["policy_name"], result["spec"])
76
+ if rtv["passed"]:
77
+ print(f" ✓ Round-trip verification: PASSED")
78
+ if rtv.get("details"):
79
+ for d in rtv["details"]:
80
+ print(f" → {d}")
81
+ else:
82
+ print(f" ✗ Round-trip verification: FAILED — {rtv.get('error', '')}")
83
+ else:
84
+ print(f" ✗ Error: {result.get('error', 'Generation failed')}")
85
+ sys.exit(1)
86
+
87
+
88
+ def _cmd_check(tool_call: str):
89
+ try:
90
+ raw = json.loads(tool_call)
91
+ except json.JSONDecodeError:
92
+ try:
93
+ with open(tool_call) as f:
94
+ raw = json.load(f)
95
+ except (FileNotFoundError, json.JSONDecodeError):
96
+ print(" ✗ Error: Provide valid JSON string or path to JSON file")
97
+ sys.exit(1)
98
+
99
+ from orchestrator import evaluate_tool_call
100
+ result = evaluate_tool_call(json.dumps(raw))
101
+ print(json.dumps(result, indent=2))
102
+
103
+
104
+ def _cmd_test(policy: str | None):
105
+ import subprocess, sys as _sys
106
+ cmd = [_sys.executable, "-m", "pytest", "tests/", "-v"]
107
+ if policy:
108
+ test_file = f"tests/test_{policy}.py"
109
+ cmd = [_sys.executable, "-m", "pytest", test_file, "-v"]
110
+ result = subprocess.run(cmd, capture_output=True, text=True)
111
+ print(result.stdout)
112
+ if result.returncode != 0:
113
+ print(result.stderr)
114
+ _sys.exit(result.returncode)
115
+
116
+
117
+ def _cmd_run(policies: list[str]):
118
+ from verifier.verifier import Verifier
119
+ v = Verifier()
120
+ print(f" Verifier running with {len(policies)} policy(ies): {', '.join(policies)}")
121
+ for p in policies:
122
+ if p not in v._policies and p != "all":
123
+ print(f" ⚠ Policy '{p}' not registered")
124
+ print(f" Listening for tool calls...")
125
+
126
+
127
+ def _cmd_status():
128
+ from pathlib import Path
129
+ from policy_store.store import PolicyStore
130
+ store = PolicyStore(Path("policy_store"))
131
+ store.load()
132
+ print(f" Policy Store: {'healthy' if store.policies else 'empty'}")
133
+ print(f" Active policies: {len(store.policies)}")
134
+ for name in store.policies:
135
+ print(f" - {name}")
136
+ from config import POLICY_ROUTES
137
+ print(f" Routes: {len(POLICY_ROUTES)}")
138
+ for tool, policy in POLICY_ROUTES.items():
139
+ print(f" {tool} → {policy}")
140
+
141
+
142
+ def _cmd_hot_reload(policy_file: str):
143
+ from pathlib import Path
144
+ from policy_store.store import PolicyStore
145
+ store = PolicyStore(Path("policy_store"))
146
+ store.hot_reload(Path(policy_file))
147
+ print(f" ✓ Hot-reload complete: {policy_file}")
148
+
149
+
150
+ def _cmd_rollback(version: str):
151
+ from pathlib import Path
152
+ from policy_store.store import PolicyStore
153
+ store = PolicyStore(Path("policy_store"))
154
+ store.rollback(version)
155
+ print(f" ✓ Rolled back to {version}")
156
+
157
+
158
+ def _cmd_dashboard(port: int):
159
+ print(f" Launching dashboard on port {port}...")
160
+ print(f" Run: streamlit run dashboard/app.py --server.port={port}")
161
+
162
+
163
+ def _cmd_verify():
164
+ import subprocess, sys as _sys
165
+ print(" Running verification checks...")
166
+ r1 = subprocess.run([_sys.executable, "-m", "pytest", "tests/", "-x", "-q"], capture_output=True, text=True)
167
+ print(r1.stdout)
168
+ if r1.returncode != 0:
169
+ print(" ✗ Tests failed")
170
+ print(r1.stderr)
171
+ _sys.exit(1)
172
+ r2 = subprocess.run(["lean", "Lean/Policy.lean"], capture_output=True, text=True)
173
+ if r2.returncode == 0:
174
+ print(" ✓ Lean theorem compiles")
175
+ else:
176
+ print(" ✗ Lean theorem failed")
177
+ print(r2.stderr)
178
+ _sys.exit(1)
179
+ print(" ✓ All checks passed")
180
+
181
+
182
+ def _cmd_wrap(framework: str):
183
+ print(f" Wrapping {framework}...")
184
+ print(f" Import veritool.integrations.{framework} and apply middleware")