iris-security-cli 0.2.0__tar.gz → 0.2.5__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.
Files changed (59) hide show
  1. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/PKG-INFO +4 -4
  2. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/certify.py +114 -13
  3. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/framework_suggest.py +117 -0
  4. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/main.py +1 -1
  5. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/regulatory.py +36 -0
  6. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_security_cli.egg-info/PKG-INFO +4 -4
  7. iris_security_cli-0.2.5/iris_security_cli.egg-info/requires.txt +8 -0
  8. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/pyproject.toml +4 -4
  9. iris_security_cli-0.2.0/iris_security_cli.egg-info/requires.txt +0 -8
  10. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/README.md +0 -0
  11. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/__init__.py +0 -0
  12. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/action_plan.py +0 -0
  13. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/assess.py +0 -0
  14. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/cedar_parser.py +0 -0
  15. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/compiler_config.py +0 -0
  16. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/compliance_check_cmd.py +0 -0
  17. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/compliance_fix.py +0 -0
  18. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/cost.py +0 -0
  19. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/declare.py +0 -0
  20. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/delegation.py +0 -0
  21. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/dlp_cmd.py +0 -0
  22. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/drift.py +0 -0
  23. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/enforce.py +0 -0
  24. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/entitlements_cmd.py +0 -0
  25. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/evidence.py +0 -0
  26. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/explain.py +0 -0
  27. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/framework_test.py +0 -0
  28. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/hitl.py +0 -0
  29. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/license_cmd.py +0 -0
  30. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/mcp_server.py +0 -0
  31. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/models_cmd.py +0 -0
  32. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/policy_cache.py +0 -0
  33. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/policy_diff.py +0 -0
  34. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/preview.py +0 -0
  35. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/quickstart.py +0 -0
  36. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/redteam.py +0 -0
  37. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/scan_govern.py +0 -0
  38. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/scan_report.py +0 -0
  39. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/scm.py +0 -0
  40. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/sentinel.py +0 -0
  41. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/status_cmd.py +0 -0
  42. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/users.py +0 -0
  43. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/vault.py +0 -0
  44. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/watch.py +0 -0
  45. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_cli/witness.py +0 -0
  46. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_security_cli.egg-info/SOURCES.txt +0 -0
  47. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_security_cli.egg-info/dependency_links.txt +0 -0
  48. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_security_cli.egg-info/entry_points.txt +0 -0
  49. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/iris_security_cli.egg-info/top_level.txt +0 -0
  50. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/setup.cfg +0 -0
  51. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_entitlements_display.py +0 -0
  52. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_evidence.py +0 -0
  53. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_framework_suggest.py +0 -0
  54. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_framework_test.py +0 -0
  55. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_hitl_cli.py +0 -0
  56. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_mcp_model_governance.py +0 -0
  57. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_policy_diff.py +0 -0
  58. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_quickstart.py +0 -0
  59. {iris_security_cli-0.2.0 → iris_security_cli-0.2.5}/tests/test_vocabulary.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-cli
3
- Version: 0.2.0
3
+ Version: 0.2.5
4
4
  Summary: IRIS CLI — declare, compile, preview, enforce, witness, certify, sentinel
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
@@ -15,13 +15,13 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: iris-security-core>=0.1.8
19
- Requires-Dist: iris-security-sdk>=0.2.0
18
+ Requires-Dist: iris-security-core>=0.1.13
19
+ Requires-Dist: iris-security-sdk>=0.2.5
20
20
  Requires-Dist: click>=8.1
21
21
  Requires-Dist: rich>=13.0
22
22
  Requires-Dist: pyyaml>=6.0
23
23
  Provides-Extra: scm
24
- Requires-Dist: iris-security-scm[all]>=0.1.1; extra == "scm"
24
+ Requires-Dist: iris-security-scm[all]>=0.2.0; extra == "scm"
25
25
 
26
26
  # iris-security-cli
27
27
 
@@ -10,6 +10,7 @@ import click
10
10
  from rich.console import Console
11
11
  from rich.panel import Panel
12
12
 
13
+ from iris_core.compliance.dynamic_loader import ComplianceRule, DynamicBundleLoader
13
14
  from iris_core.entitlements import Entitlements, Feature
14
15
  from iris_core.models.passport import AgentPassport
15
16
 
@@ -26,20 +27,60 @@ from iris_cli.framework_test import (
26
27
  console = Console()
27
28
 
28
29
 
30
+ def _evaluate_custom_rule(rule: ComplianceRule, passport: AgentPassport) -> str:
31
+ safe_globals = {"__builtins__": {}}
32
+ safe_locals = {
33
+ "passport": passport,
34
+ "hitl_config": getattr(passport, "hitl_config", None),
35
+ "cost_config": getattr(passport, "cost_config", None),
36
+ "bool": bool,
37
+ "len": len,
38
+ }
39
+ try:
40
+ value = eval(rule.check_expression, safe_globals, safe_locals)
41
+ except Exception:
42
+ return "FAIL"
43
+ return "PASS" if value else "FAIL"
44
+
45
+
46
+ def _rule_source_label(loader: DynamicBundleLoader) -> str:
47
+ info = loader.registry_info()
48
+ if info.source == "live":
49
+ return "Live registry"
50
+ if info.source == "cache":
51
+ hours = info.cache_age_hours
52
+ if hours is not None and hours < 1:
53
+ return "Live registry (updated recently)"
54
+ if hours is not None:
55
+ return f"Live registry (updated {int(hours)} hours ago)"
56
+ return "Cached registry"
57
+ return "Bundled fallback"
58
+
59
+
29
60
  def _render_certification_header(
30
61
  framework: str,
31
62
  agent_name: str,
32
63
  result,
64
+ *,
65
+ loader: DynamicBundleLoader,
66
+ custom_rule_count: int,
33
67
  ) -> None:
68
+ bundle = loader.load_bundle(framework)
69
+ info = loader.registry_info()
34
70
  framework_label = _framework_name(framework)
35
71
  if framework == "colorado-ai-act":
36
72
  framework_label = "Colorado AI Act (SB 26-189)"
37
73
 
74
+ version = bundle.metadata.get("current_version", info.registry_version)
75
+ law_name = bundle.metadata.get("law_name", framework_label)
76
+
38
77
  console.print(Panel(
39
- f"Agent: {agent_name}\n"
40
- f"Framework: {framework_label}\n"
41
- f"Generated: {datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')}\n"
42
- f"Attested: IRIS · Commit: local",
78
+ f"Agent: {agent_name}\n"
79
+ f"Framework: {law_name} v{version}\n"
80
+ f"Rule source: {_rule_source_label(loader)}\n"
81
+ f"Custom rules: {custom_rule_count} organization rule{'s' if custom_rule_count != 1 else ''} evaluated\n"
82
+ f"Generated: {datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')}\n"
83
+ f"Attested: IRIS · Commit: local",
43
84
  title=f"IRIS Certification — {framework}",
44
85
  style="blue",
45
86
  ))
@@ -63,25 +104,72 @@ def _render_certification_header(
63
104
  )
64
105
 
65
106
 
107
+ def _render_custom_rules_section(
108
+ custom_rules: list[ComplianceRule],
109
+ passport: AgentPassport,
110
+ ) -> None:
111
+ if not custom_rules:
112
+ return
113
+ console.print("\n[bold]ORGANIZATION POLICY[/bold]")
114
+ console.print("─" * 65)
115
+ for rule in custom_rules:
116
+ status = _evaluate_custom_rule(rule, passport)
117
+ icon = "[green]✓[/green]" if status == "PASS" else "[red]✗[/red]"
118
+ console.print(f"{icon} {rule.rule_id:<14} {rule.name}")
119
+ if status != "PASS" and rule.remediation_command:
120
+ console.print(f" Fix: {rule.remediation_command}")
121
+
122
+
66
123
  @click.command("certify")
67
124
  @click.option(
68
125
  "--framework",
69
126
  required=True,
70
- type=click.Choice(sorted(["colorado-ai-act", "nist-ai-rmf", "fedramp-moderate", "hipaa", "soc2", "gdpr"])),
127
+ type=click.Choice(sorted([
128
+ "colorado-ai-act", "nist-ai-rmf", "fedramp-moderate", "hipaa", "soc2", "gdpr",
129
+ "nyc-ll144", "illinois-ai-video", "eu-ai-act", "ccpa-admt", "china-pipl",
130
+ ])),
71
131
  )
72
132
  @click.option("--agent", "agent_name", required=True, help="Agent name under governance/agents")
73
133
  @click.option("--format", "output_format", default="table", type=click.Choice(["table", "json", "markdown"]))
74
- def certify_cmd(framework: str, agent_name: str, output_format: str) -> None:
134
+ @click.option(
135
+ "--registry-version",
136
+ default=None,
137
+ help="Pin to a specific registry version for reproducible certifications",
138
+ )
139
+ @click.option("--dir", "governance_dir", type=click.Path(path_type=Path), default=None)
140
+ @click.option("--offline", is_flag=True, help="Use bundled registry only (no network refresh)")
141
+ def certify_cmd(
142
+ framework: str,
143
+ agent_name: str,
144
+ output_format: str,
145
+ registry_version: Optional[str],
146
+ governance_dir: Optional[Path],
147
+ offline: bool,
148
+ ) -> None:
75
149
  """
76
150
  Certify compliance readiness against a regulatory framework.
77
151
 
78
152
  Alias: iris test
79
153
  """
80
- passport_path = Path.cwd() / "governance" / "agents" / agent_name / "passport.yaml"
154
+ gov_dir = governance_dir or Path.cwd() / "governance"
155
+ passport_path = gov_dir / "agents" / agent_name / "passport.yaml"
81
156
  if not passport_path.exists():
82
157
  raise click.ClickException(f"Passport not found: {passport_path}")
83
158
  passport = AgentPassport.from_yaml(passport_path.read_text())
84
159
 
160
+ loader = DynamicBundleLoader(pinned_version=registry_version)
161
+
162
+ if not offline and not registry_version:
163
+ if not loader.is_cache_fresh():
164
+ console.print("[dim]Checking for regulatory updates...[/dim]")
165
+ updated = loader.refresh_registry()
166
+ if updated:
167
+ console.print("[green]✓ Regulatory intelligence updated[/green]")
168
+ console.print("[dim] Run: iris regulatory check for change summary[/dim]")
169
+
170
+ custom_rules = loader.load_custom_rules(gov_dir)
171
+ loader.get_effective_rules(framework, gov_dir)
172
+
85
173
  ents = Entitlements()
86
174
  has_pro = ents.has(Feature.CLI_TEST_FULL_REPORT)
87
175
 
@@ -97,13 +185,22 @@ def certify_cmd(framework: str, agent_name: str, output_format: str) -> None:
97
185
  elif output_format == "markdown":
98
186
  click.echo(_render_markdown(result, has_pro))
99
187
  else:
100
- _render_certification_header(framework, agent_name, result)
188
+ _render_certification_header(
189
+ framework, agent_name, result,
190
+ loader=loader,
191
+ custom_rule_count=len(custom_rules),
192
+ )
101
193
  _render_table(result, has_pro)
102
- _render_response_configuration(framework, passport)
194
+ _render_custom_rules_section(custom_rules, passport)
195
+ _render_response_configuration(framework, passport, loader=loader)
103
196
 
104
197
 
105
- def _render_response_configuration(framework: str, passport: AgentPassport) -> None:
106
- from iris_core.compliance.framework_check import load_bundle_data
198
+ def _render_response_configuration(
199
+ framework: str,
200
+ passport: AgentPassport,
201
+ *,
202
+ loader: Optional[DynamicBundleLoader] = None,
203
+ ) -> None:
107
204
  from iris_core.compliance.violation_response import (
108
205
  get_effective_response,
109
206
  rule_response_label,
@@ -111,7 +208,11 @@ def _render_response_configuration(framework: str, passport: AgentPassport) -> N
111
208
  )
112
209
 
113
210
  try:
114
- bundle = load_bundle_data(framework)
211
+ if loader:
212
+ bundle = loader.load_bundle(framework).to_dict()
213
+ else:
214
+ from iris_core.compliance.framework_check import load_bundle_data
215
+ bundle = load_bundle_data(framework)
115
216
  except ValueError:
116
217
  return
117
218
  rules = bundle.get("rules", [])
@@ -131,7 +232,7 @@ def _render_response_configuration(framework: str, passport: AgentPassport) -> N
131
232
  if base == ViolationResponse.BLOCK:
132
233
  line += " (unconditional — cannot be overridden)"
133
234
  elif effective != default:
134
- line += f" (override active)"
235
+ line += " (override active)"
135
236
  else:
136
237
  line += " (default)"
137
238
  console.print(line)
@@ -44,6 +44,25 @@ CONVERSATIONAL_KEYWORDS = (
44
44
  "chat bot",
45
45
  )
46
46
 
47
+ EMPLOYMENT_KEYWORDS = (
48
+ "hiring",
49
+ "recruiting",
50
+ "recruitment",
51
+ "hr",
52
+ "ats",
53
+ "screening",
54
+ "employment",
55
+ "interview",
56
+ "resume",
57
+ "candidate",
58
+ )
59
+
60
+ VIDEO_KEYWORDS = (
61
+ "video",
62
+ "interview",
63
+ "biometric",
64
+ )
65
+
47
66
  Q1_CHOICES = [
48
67
  "Makes decisions that affect individual people (hiring, loans, medical, etc.)",
49
68
  "Automates business processes (summarization, classification, routing)",
@@ -267,6 +286,48 @@ def build_recommendations(answers: dict[str, Any]) -> list[Recommendation]:
267
286
  soc2 = q2 == Q2_CHOICES[5] or "SOC 2 (SaaS / enterprise)" in q7
268
287
  gdpr = "Outside the US" in q8 or "Other / multiple states" in q8
269
288
 
289
+ ccpa_admt = (
290
+ "California" in q8
291
+ and q5
292
+ and (
293
+ q1 == Q1_CHOICES[0]
294
+ or q2 in {Q2_CHOICES[2], Q2_CHOICES[3]}
295
+ )
296
+ )
297
+ china_pipl = (
298
+ "Outside the US" in q8
299
+ or "china" in agent_description
300
+ or "asia" in agent_description
301
+ or (q2 == Q2_CHOICES[4] and "Outside the US" in q8)
302
+ )
303
+
304
+ employment_domain = (
305
+ q1 == Q1_CHOICES[0]
306
+ or any(kw in q1.lower() for kw in EMPLOYMENT_KEYWORDS)
307
+ or any(kw in agent_description for kw in EMPLOYMENT_KEYWORDS)
308
+ or q2 in {Q2_CHOICES[4], Q2_CHOICES[6]}
309
+ )
310
+ illinois_video = (
311
+ employment_domain
312
+ and (
313
+ "Illinois" in q8
314
+ or "Other / multiple states" in q8
315
+ )
316
+ and (
317
+ any(kw in agent_description for kw in VIDEO_KEYWORDS)
318
+ or q4 == Q4_CHOICES[3]
319
+ or "video" in q1.lower()
320
+ or "interview" in q1.lower()
321
+ )
322
+ )
323
+ nyc_ll144 = (
324
+ employment_domain
325
+ and (
326
+ "New York" in q8
327
+ or "Other / multiple states" in q8
328
+ )
329
+ )
330
+
270
331
  colorado_reason = (
271
332
  "Your agent makes consequential decisions for Colorado users. "
272
333
  "SB 26-189 applies (replaces SB 24-205, effective Jan. 1, 2027). "
@@ -378,6 +439,62 @@ def build_recommendations(answers: dict[str, Any]) -> list[Recommendation]:
378
439
  ),
379
440
  command="iris test --framework gdpr" if gdpr else None,
380
441
  ),
442
+ Recommendation(
443
+ framework="ccpa-admt",
444
+ tier="PRO",
445
+ status="REQUIRED" if ccpa_admt else "NOT APPLICABLE",
446
+ reason=(
447
+ "California ADMT regulations (effective Jan. 1, 2026) apply to "
448
+ "automated decisions affecting California consumers in employment, "
449
+ "credit, healthcare, housing, or education."
450
+ if ccpa_admt
451
+ else "No California ADMT scope detected."
452
+ ),
453
+ command="iris compliance check --framework ccpa-admt" if ccpa_admt else None,
454
+ ),
455
+ Recommendation(
456
+ framework="china-pipl",
457
+ tier="PRO",
458
+ status="REQUIRED" if china_pipl else "NOT APPLICABLE",
459
+ reason=(
460
+ "China PIPL applies when processing personal information of "
461
+ "individuals located in China, including automated decision-making "
462
+ "and cross-border transfer restrictions."
463
+ if china_pipl
464
+ else "No China or Asia-Pacific user footprint detected."
465
+ ),
466
+ command="iris compliance check --framework china-pipl" if china_pipl else None,
467
+ ),
468
+ Recommendation(
469
+ framework="illinois-ai-video",
470
+ tier="PRO",
471
+ status="REQUIRED" if illinois_video else "NOT APPLICABLE",
472
+ reason=(
473
+ "Illinois AI Video Interview Act applies to AI analysis of video "
474
+ "interviews for Illinois-based positions. Consent must be logged "
475
+ "before any video analysis (820 ILCS 42)."
476
+ if illinois_video
477
+ else "No Illinois video interview AI scope detected."
478
+ ),
479
+ command=(
480
+ "iris compliance check --framework illinois-ai-video"
481
+ if illinois_video
482
+ else None
483
+ ),
484
+ ),
485
+ Recommendation(
486
+ framework="nyc-ll144",
487
+ tier="PRO",
488
+ status="REQUIRED" if nyc_ll144 else "NOT APPLICABLE",
489
+ reason=(
490
+ "NYC Local Law 144 requires annual independent bias audits, public "
491
+ "disclosure, and candidate notice before using Automated Employment "
492
+ "Decision Tools (AEDTs) for NYC hiring."
493
+ if nyc_ll144
494
+ else "No NYC AEDT hiring scope detected."
495
+ ),
496
+ command="iris compliance check --framework nyc-ll144" if nyc_ll144 else None,
497
+ ),
381
498
  ]
382
499
  return recommendations
383
500
 
@@ -24,7 +24,7 @@ console = Console()
24
24
 
25
25
 
26
26
  @click.group()
27
- @click.version_option(version="0.2.0", prog_name="iris")
27
+ @click.version_option(version="0.2.5", prog_name="iris")
28
28
  def cli():
29
29
  """
30
30
  IRIS — Policy as Code for AI Agents
@@ -50,6 +50,41 @@ def _format_effective_date(iso_date: str) -> str:
50
50
  return iso_date
51
51
 
52
52
 
53
+ def _render_rule_changes(bundle_id: str, old_version: str, new_version: str) -> None:
54
+ """Show rule-level changes when registry versions differ."""
55
+ import json
56
+
57
+ from iris_core.compliance.dynamic_loader import DynamicBundleLoader, _bundled_registry_path
58
+
59
+ loader = DynamicBundleLoader()
60
+ cache_path = Path.home() / ".iris" / "registry-cache.json"
61
+ bundled = _bundled_registry_path()
62
+
63
+ old_registry: dict = {}
64
+ if cache_path.exists():
65
+ try:
66
+ old_registry = json.loads(cache_path.read_text())
67
+ except Exception:
68
+ pass
69
+ elif bundled.exists():
70
+ old_registry = json.loads(bundled.read_text())
71
+
72
+ if not bundled.exists():
73
+ return
74
+ new_registry = json.loads(bundled.read_text())
75
+ changes = loader.diff_rules(bundle_id, old_registry, new_registry)
76
+ if not changes:
77
+ return
78
+
79
+ console.print(f"\n [bold]REGULATORY UPDATE — {bundle_id} v{old_version} → v{new_version}[/bold]")
80
+ console.print(" [bold]RULE CHANGES:[/bold]")
81
+ for change in changes[:10]:
82
+ console.print(f" ● {change}")
83
+ console.print(
84
+ "\n Run: [bold]iris certify --agent <name>[/bold] to see updated status"
85
+ )
86
+
87
+
53
88
  def _render_check_output(
54
89
  tracker: RegulatoryTracker,
55
90
  updates: list,
@@ -101,6 +136,7 @@ def _render_check_output(
101
136
  console.print(
102
137
  f"Effective: {_format_effective_date(update.effective_date)}"
103
138
  )
139
+ _render_rule_changes(update.bundle_id, update.current_installed_version, update.available_version)
104
140
  console.print(" Action required:")
105
141
  console.print(
106
142
  " 1. Run: [bold]pip install --upgrade iris-security-sdk[/bold]"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-cli
3
- Version: 0.2.0
3
+ Version: 0.2.5
4
4
  Summary: IRIS CLI — declare, compile, preview, enforce, witness, certify, sentinel
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
@@ -15,13 +15,13 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: iris-security-core>=0.1.8
19
- Requires-Dist: iris-security-sdk>=0.2.0
18
+ Requires-Dist: iris-security-core>=0.1.13
19
+ Requires-Dist: iris-security-sdk>=0.2.5
20
20
  Requires-Dist: click>=8.1
21
21
  Requires-Dist: rich>=13.0
22
22
  Requires-Dist: pyyaml>=6.0
23
23
  Provides-Extra: scm
24
- Requires-Dist: iris-security-scm[all]>=0.1.1; extra == "scm"
24
+ Requires-Dist: iris-security-scm[all]>=0.2.0; extra == "scm"
25
25
 
26
26
  # iris-security-cli
27
27
 
@@ -0,0 +1,8 @@
1
+ iris-security-core>=0.1.13
2
+ iris-security-sdk>=0.2.5
3
+ click>=8.1
4
+ rich>=13.0
5
+ pyyaml>=6.0
6
+
7
+ [scm]
8
+ iris-security-scm[all]>=0.2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iris-security-cli"
7
- version = "0.2.0"
7
+ version = "0.2.5"
8
8
  description = "IRIS CLI — declare, compile, preview, enforce, witness, certify, sentinel"
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -20,15 +20,15 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.12",
21
21
  ]
22
22
  dependencies = [
23
- "iris-security-core>=0.1.8",
24
- "iris-security-sdk>=0.2.0",
23
+ "iris-security-core>=0.1.13",
24
+ "iris-security-sdk>=0.2.5",
25
25
  "click>=8.1",
26
26
  "rich>=13.0",
27
27
  "pyyaml>=6.0",
28
28
  ]
29
29
 
30
30
  [project.optional-dependencies]
31
- scm = ["iris-security-scm[all]>=0.1.1"]
31
+ scm = ["iris-security-scm[all]>=0.2.0"]
32
32
 
33
33
  [project.urls]
34
34
  Homepage = "https://github.com/gimartinb/iris-sdk"
@@ -1,8 +0,0 @@
1
- iris-security-core>=0.1.8
2
- iris-security-sdk>=0.2.0
3
- click>=8.1
4
- rich>=13.0
5
- pyyaml>=6.0
6
-
7
- [scm]
8
- iris-security-scm[all]>=0.1.1