iris-security-sdk 0.1.2__tar.gz → 0.1.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-sdk
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: IRIS — AI Agent Governance SDK. Govern AI agents locally.
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
@@ -16,30 +16,31 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Requires-Python: >=3.10
18
18
  Description-Content-Type: text/markdown
19
- Requires-Dist: iris-security-core>=0.1.1
19
+ Requires-Dist: iris-security-core>=0.1.3
20
20
  Requires-Dist: pyyaml>=6.0
21
21
  Requires-Dist: pydantic>=2.0
22
22
  Requires-Dist: click>=8.1
23
23
  Requires-Dist: rich>=13.0
24
+ Requires-Dist: httpx>=0.27
24
25
  Provides-Extra: anthropic
25
- Requires-Dist: iris-security-anthropic>=0.1.0; extra == "anthropic"
26
+ Requires-Dist: iris-security-anthropic>=0.1.1; extra == "anthropic"
26
27
  Provides-Extra: openai
27
- Requires-Dist: iris-security-openai>=0.1.0; extra == "openai"
28
+ Requires-Dist: iris-security-openai>=0.1.1; extra == "openai"
28
29
  Provides-Extra: google
29
- Requires-Dist: iris-security-gemini>=0.1.0; extra == "google"
30
+ Requires-Dist: iris-security-gemini>=0.1.1; extra == "google"
30
31
  Provides-Extra: vertexai
31
- Requires-Dist: iris-security-vertexai>=0.1.0; extra == "vertexai"
32
+ Requires-Dist: iris-security-vertexai>=0.1.1; extra == "vertexai"
32
33
  Provides-Extra: langchain
33
- Requires-Dist: iris-security-langchain>=0.1.0; extra == "langchain"
34
+ Requires-Dist: iris-security-langchain>=0.1.1; extra == "langchain"
34
35
  Provides-Extra: crewai
35
- Requires-Dist: iris-security-crewai>=0.1.0; extra == "crewai"
36
+ Requires-Dist: iris-security-crewai>=0.1.1; extra == "crewai"
36
37
  Provides-Extra: all
37
- Requires-Dist: iris-security-anthropic>=0.1.0; extra == "all"
38
- Requires-Dist: iris-security-openai>=0.1.0; extra == "all"
39
- Requires-Dist: iris-security-gemini>=0.1.0; extra == "all"
40
- Requires-Dist: iris-security-vertexai>=0.1.0; extra == "all"
41
- Requires-Dist: iris-security-langchain>=0.1.0; extra == "all"
42
- Requires-Dist: iris-security-crewai>=0.1.0; extra == "all"
38
+ Requires-Dist: iris-security-anthropic>=0.1.1; extra == "all"
39
+ Requires-Dist: iris-security-openai>=0.1.1; extra == "all"
40
+ Requires-Dist: iris-security-gemini>=0.1.1; extra == "all"
41
+ Requires-Dist: iris-security-vertexai>=0.1.1; extra == "all"
42
+ Requires-Dist: iris-security-langchain>=0.1.1; extra == "all"
43
+ Requires-Dist: iris-security-crewai>=0.1.1; extra == "all"
43
44
  Provides-Extra: dev
44
45
  Requires-Dist: pytest>=8.0; extra == "dev"
45
46
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
@@ -23,6 +23,7 @@ from __future__ import annotations
23
23
  from typing import Optional, List, Callable, Any
24
24
  from functools import wraps
25
25
  from pathlib import Path
26
+ from datetime import datetime, timedelta
26
27
  import os
27
28
  import sys
28
29
 
@@ -40,8 +41,10 @@ from iris_core.rbac.context import UserContext
40
41
  from iris_core.engine.compiler import PolicyCompiler, CompilationResult
41
42
  from iris_core.compliance.registry import ComplianceRegistry
42
43
  from iris_core.evidence.vault import EvidenceVault
44
+ from iris_core.cost.tracker import CostSummary, CostTracker, CostEntry
45
+ from iris_core.cost.pricing import PricingRegistry
43
46
 
44
- __version__ = "0.1.0"
47
+ __version__ = "0.1.4"
45
48
  __all__ = [
46
49
  # Main classes
47
50
  "IrisAgent",
@@ -63,6 +66,10 @@ __all__ = [
63
66
  "CompilationResult",
64
67
  "ComplianceRegistry",
65
68
  "EvidenceVault",
69
+ "CostTracker",
70
+ "CostSummary",
71
+ "CostEntry",
72
+ "PricingRegistry",
66
73
  ]
67
74
 
68
75
 
@@ -131,6 +138,10 @@ class IrisAgent:
131
138
  self._engine = CedarEngine(policy_dir=self._policy_dir)
132
139
  self._compiler: Optional[PolicyCompiler] = None
133
140
  self._vault = EvidenceVault(agent_id=self.passport.agent_id)
141
+ self._cost_tracker = CostTracker(
142
+ agent_id=self.passport.agent_id,
143
+ agent_name=self.passport.name,
144
+ )
134
145
  self._telemetry = telemetry
135
146
 
136
147
  # Load policy if it exists on disk
@@ -203,8 +214,7 @@ class IrisAgent:
203
214
  data_classification=data_classification,
204
215
  **self._user_ctx.evaluation_fields(),
205
216
  )
206
- result = self._engine.evaluate(self.passport, ctx)
207
- self._vault.record(ctx, result)
217
+ result = self.evaluate(ctx)
208
218
 
209
219
  if result.decision == "DENY":
210
220
  raise IrisViolationError(result)
@@ -220,6 +230,9 @@ class IrisAgent:
220
230
  """Direct policy evaluation without the decorator pattern."""
221
231
  result = self._engine.evaluate(self.passport, context)
222
232
  self._vault.record(context, result)
233
+ from iris._telemetry import maybe_fire_first_policy_run
234
+
235
+ maybe_fire_first_policy_run()
223
236
  return result
224
237
 
225
238
  def check_compliance(
@@ -236,6 +249,17 @@ class IrisAgent:
236
249
  framework or [t.value for t in self.passport.compliance_tags],
237
250
  )
238
251
 
252
+ @property
253
+ def total_cost_usd(self) -> float:
254
+ """Total LLM spend for this agent since install."""
255
+ summary = self._cost_tracker.get_summary()
256
+ return summary.total_cost_usd
257
+
258
+ def cost_report(self, days: int = 30) -> CostSummary:
259
+ """Return a cost summary for the last N days."""
260
+ since = (datetime.utcnow() - timedelta(days=days)).isoformat()
261
+ return self._cost_tracker.get_summary(since=since)
262
+
239
263
  @property
240
264
  def is_ready_for_production(self) -> bool:
241
265
  """Quick check: is this agent compliant enough for production?"""
@@ -0,0 +1,119 @@
1
+ """First-run telemetry for IRIS SDK installs. Opt-out via IRIS_TELEMETRY_OPT_OUT=1."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import sys
8
+ import threading
9
+ import uuid
10
+ from datetime import datetime, timezone
11
+ from importlib.metadata import PackageNotFoundError, version
12
+ from pathlib import Path
13
+
14
+ from iris._telemetry_config import TELEMETRY_ENABLED, TELEMETRY_ENDPOINT
15
+
16
+ _IRIS_DIR = Path.home() / ".iris"
17
+ _INSTALL_ID_FILE = _IRIS_DIR / "install_id"
18
+ _FIRST_RUN_SENTINEL = _IRIS_DIR / ".telemetry_sent"
19
+ _FIRST_POLICY_SENTINEL = _IRIS_DIR / ".first_policy_sent"
20
+
21
+
22
+ def detect_install_method() -> str:
23
+ """Detect how the iris CLI was installed from the executable path."""
24
+ iris_path = shutil.which("iris")
25
+ if not iris_path:
26
+ return "unknown"
27
+
28
+ path_lower = iris_path.lower()
29
+ segments = [segment.lower() for segment in Path(iris_path).parts]
30
+
31
+ if "pipx" in segments or "pipx" in path_lower:
32
+ return "pipx"
33
+ if "uv" in segments or ".uv" in path_lower:
34
+ return "uv"
35
+ if "site-packages" in segments or "dist-packages" in segments:
36
+ return "pip"
37
+ return "unknown"
38
+
39
+
40
+ def telemetry_opted_out() -> bool:
41
+ return os.environ.get("IRIS_TELEMETRY_OPT_OUT", "").strip() in {"1", "true", "yes"}
42
+
43
+
44
+ def telemetry_enabled() -> bool:
45
+ return TELEMETRY_ENABLED and not telemetry_opted_out()
46
+
47
+
48
+ def _get_or_create_install_id() -> str:
49
+ _IRIS_DIR.mkdir(parents=True, exist_ok=True)
50
+ if _INSTALL_ID_FILE.exists():
51
+ install_id = _INSTALL_ID_FILE.read_text(encoding="utf-8").strip()
52
+ if install_id:
53
+ return install_id
54
+
55
+ install_id = str(uuid.uuid4())
56
+ _INSTALL_ID_FILE.write_text(install_id, encoding="utf-8")
57
+ return install_id
58
+
59
+
60
+ def _get_iris_version() -> str:
61
+ try:
62
+ return version("iris-security-sdk")
63
+ except PackageNotFoundError:
64
+ return "unknown"
65
+
66
+
67
+ def _build_payload(event: str) -> dict[str, str]:
68
+ return {
69
+ "event": event,
70
+ "install_id": _get_or_create_install_id(),
71
+ "install_method": detect_install_method(),
72
+ "python_version": sys.version,
73
+ "platform": sys.platform,
74
+ "iris_version": _get_iris_version(),
75
+ "timestamp": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
76
+ }
77
+
78
+
79
+ def _send_event(event: str) -> None:
80
+ try:
81
+ import httpx
82
+
83
+ httpx.post(TELEMETRY_ENDPOINT, json=_build_payload(event), timeout=2.0)
84
+ except Exception:
85
+ pass
86
+
87
+
88
+ def _maybe_fire_once(event: str, sentinel: Path) -> None:
89
+ if not telemetry_enabled():
90
+ return
91
+
92
+ if sentinel.exists():
93
+ return
94
+
95
+ try:
96
+ _IRIS_DIR.mkdir(parents=True, exist_ok=True)
97
+ sentinel.touch()
98
+ except OSError:
99
+ return
100
+
101
+ thread = threading.Thread(target=_send_event, args=(event,), daemon=True)
102
+ thread.start()
103
+
104
+
105
+ def maybe_fire_first_run() -> None:
106
+ """Fire a one-time first-run telemetry event on this machine."""
107
+ _maybe_fire_once("first_run", _FIRST_RUN_SENTINEL)
108
+
109
+
110
+ def maybe_fire_first_policy_run() -> None:
111
+ """Fire a one-time telemetry event after the first policy evaluation."""
112
+ _maybe_fire_once("first_policy_run", _FIRST_POLICY_SENTINEL)
113
+
114
+
115
+ def send_ping() -> None:
116
+ """Fire a test telemetry ping event (internal verification)."""
117
+ if not telemetry_enabled():
118
+ return
119
+ _send_event("ping")
@@ -0,0 +1,4 @@
1
+ """Telemetry configuration for IRIS SDK first-run events."""
2
+
3
+ TELEMETRY_ENDPOINT = "https://telemetry.iris-security.dev/events"
4
+ TELEMETRY_ENABLED = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-sdk
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: IRIS — AI Agent Governance SDK. Govern AI agents locally.
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
@@ -16,30 +16,31 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Requires-Python: >=3.10
18
18
  Description-Content-Type: text/markdown
19
- Requires-Dist: iris-security-core>=0.1.1
19
+ Requires-Dist: iris-security-core>=0.1.3
20
20
  Requires-Dist: pyyaml>=6.0
21
21
  Requires-Dist: pydantic>=2.0
22
22
  Requires-Dist: click>=8.1
23
23
  Requires-Dist: rich>=13.0
24
+ Requires-Dist: httpx>=0.27
24
25
  Provides-Extra: anthropic
25
- Requires-Dist: iris-security-anthropic>=0.1.0; extra == "anthropic"
26
+ Requires-Dist: iris-security-anthropic>=0.1.1; extra == "anthropic"
26
27
  Provides-Extra: openai
27
- Requires-Dist: iris-security-openai>=0.1.0; extra == "openai"
28
+ Requires-Dist: iris-security-openai>=0.1.1; extra == "openai"
28
29
  Provides-Extra: google
29
- Requires-Dist: iris-security-gemini>=0.1.0; extra == "google"
30
+ Requires-Dist: iris-security-gemini>=0.1.1; extra == "google"
30
31
  Provides-Extra: vertexai
31
- Requires-Dist: iris-security-vertexai>=0.1.0; extra == "vertexai"
32
+ Requires-Dist: iris-security-vertexai>=0.1.1; extra == "vertexai"
32
33
  Provides-Extra: langchain
33
- Requires-Dist: iris-security-langchain>=0.1.0; extra == "langchain"
34
+ Requires-Dist: iris-security-langchain>=0.1.1; extra == "langchain"
34
35
  Provides-Extra: crewai
35
- Requires-Dist: iris-security-crewai>=0.1.0; extra == "crewai"
36
+ Requires-Dist: iris-security-crewai>=0.1.1; extra == "crewai"
36
37
  Provides-Extra: all
37
- Requires-Dist: iris-security-anthropic>=0.1.0; extra == "all"
38
- Requires-Dist: iris-security-openai>=0.1.0; extra == "all"
39
- Requires-Dist: iris-security-gemini>=0.1.0; extra == "all"
40
- Requires-Dist: iris-security-vertexai>=0.1.0; extra == "all"
41
- Requires-Dist: iris-security-langchain>=0.1.0; extra == "all"
42
- Requires-Dist: iris-security-crewai>=0.1.0; extra == "all"
38
+ Requires-Dist: iris-security-anthropic>=0.1.1; extra == "all"
39
+ Requires-Dist: iris-security-openai>=0.1.1; extra == "all"
40
+ Requires-Dist: iris-security-gemini>=0.1.1; extra == "all"
41
+ Requires-Dist: iris-security-vertexai>=0.1.1; extra == "all"
42
+ Requires-Dist: iris-security-langchain>=0.1.1; extra == "all"
43
+ Requires-Dist: iris-security-crewai>=0.1.1; extra == "all"
43
44
  Provides-Extra: dev
44
45
  Requires-Dist: pytest>=8.0; extra == "dev"
45
46
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
@@ -1,6 +1,8 @@
1
1
  README.md
2
2
  pyproject.toml
3
3
  iris/__init__.py
4
+ iris/_telemetry.py
5
+ iris/_telemetry_config.py
4
6
  iris_security_sdk.egg-info/PKG-INFO
5
7
  iris_security_sdk.egg-info/SOURCES.txt
6
8
  iris_security_sdk.egg-info/dependency_links.txt
@@ -0,0 +1,39 @@
1
+ iris-security-core>=0.1.3
2
+ pyyaml>=6.0
3
+ pydantic>=2.0
4
+ click>=8.1
5
+ rich>=13.0
6
+ httpx>=0.27
7
+
8
+ [all]
9
+ iris-security-anthropic>=0.1.1
10
+ iris-security-openai>=0.1.1
11
+ iris-security-gemini>=0.1.1
12
+ iris-security-vertexai>=0.1.1
13
+ iris-security-langchain>=0.1.1
14
+ iris-security-crewai>=0.1.1
15
+
16
+ [anthropic]
17
+ iris-security-anthropic>=0.1.1
18
+
19
+ [crewai]
20
+ iris-security-crewai>=0.1.1
21
+
22
+ [dev]
23
+ pytest>=8.0
24
+ pytest-asyncio>=0.23
25
+ pytest-cov>=5.0
26
+ ruff>=0.4
27
+ mypy>=1.10
28
+
29
+ [google]
30
+ iris-security-gemini>=0.1.1
31
+
32
+ [langchain]
33
+ iris-security-langchain>=0.1.1
34
+
35
+ [openai]
36
+ iris-security-openai>=0.1.1
37
+
38
+ [vertexai]
39
+ iris-security-vertexai>=0.1.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iris-security-sdk"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "IRIS — AI Agent Governance SDK. Govern AI agents locally."
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -28,27 +28,28 @@ classifiers = [
28
28
  "Programming Language :: Python :: 3.12",
29
29
  ]
30
30
  dependencies = [
31
- "iris-security-core>=0.1.1",
31
+ "iris-security-core>=0.1.3",
32
32
  "pyyaml>=6.0",
33
33
  "pydantic>=2.0",
34
34
  "click>=8.1",
35
35
  "rich>=13.0",
36
+ "httpx>=0.27",
36
37
  ]
37
38
 
38
39
  [project.optional-dependencies]
39
- anthropic = ["iris-security-anthropic>=0.1.0"]
40
- openai = ["iris-security-openai>=0.1.0"]
41
- google = ["iris-security-gemini>=0.1.0"]
42
- vertexai = ["iris-security-vertexai>=0.1.0"]
43
- langchain = ["iris-security-langchain>=0.1.0"]
44
- crewai = ["iris-security-crewai>=0.1.0"]
40
+ anthropic = ["iris-security-anthropic>=0.1.1"]
41
+ openai = ["iris-security-openai>=0.1.1"]
42
+ google = ["iris-security-gemini>=0.1.1"]
43
+ vertexai = ["iris-security-vertexai>=0.1.1"]
44
+ langchain = ["iris-security-langchain>=0.1.1"]
45
+ crewai = ["iris-security-crewai>=0.1.1"]
45
46
  all = [
46
- "iris-security-anthropic>=0.1.0",
47
- "iris-security-openai>=0.1.0",
48
- "iris-security-gemini>=0.1.0",
49
- "iris-security-vertexai>=0.1.0",
50
- "iris-security-langchain>=0.1.0",
51
- "iris-security-crewai>=0.1.0",
47
+ "iris-security-anthropic>=0.1.1",
48
+ "iris-security-openai>=0.1.1",
49
+ "iris-security-gemini>=0.1.1",
50
+ "iris-security-vertexai>=0.1.1",
51
+ "iris-security-langchain>=0.1.1",
52
+ "iris-security-crewai>=0.1.1",
52
53
  ]
53
54
  dev = [
54
55
  "pytest>=8.0",
@@ -1,38 +0,0 @@
1
- iris-security-core>=0.1.1
2
- pyyaml>=6.0
3
- pydantic>=2.0
4
- click>=8.1
5
- rich>=13.0
6
-
7
- [all]
8
- iris-security-anthropic>=0.1.0
9
- iris-security-openai>=0.1.0
10
- iris-security-gemini>=0.1.0
11
- iris-security-vertexai>=0.1.0
12
- iris-security-langchain>=0.1.0
13
- iris-security-crewai>=0.1.0
14
-
15
- [anthropic]
16
- iris-security-anthropic>=0.1.0
17
-
18
- [crewai]
19
- iris-security-crewai>=0.1.0
20
-
21
- [dev]
22
- pytest>=8.0
23
- pytest-asyncio>=0.23
24
- pytest-cov>=5.0
25
- ruff>=0.4
26
- mypy>=1.10
27
-
28
- [google]
29
- iris-security-gemini>=0.1.0
30
-
31
- [langchain]
32
- iris-security-langchain>=0.1.0
33
-
34
- [openai]
35
- iris-security-openai>=0.1.0
36
-
37
- [vertexai]
38
- iris-security-vertexai>=0.1.0