iris-security-cli 0.1.7__tar.gz → 0.1.9__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 (58) hide show
  1. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/PKG-INFO +5 -5
  2. iris_security_cli-0.1.9/iris_cli/certify.py +105 -0
  3. iris_security_cli-0.1.9/iris_cli/compliance_check_cmd.py +180 -0
  4. iris_security_cli-0.1.9/iris_cli/compliance_fix.py +202 -0
  5. iris_security_cli-0.1.9/iris_cli/declare.py +177 -0
  6. iris_security_cli-0.1.9/iris_cli/delegation.py +172 -0
  7. iris_security_cli-0.1.9/iris_cli/enforce.py +148 -0
  8. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/license_cmd.py +1 -1
  9. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/main.py +119 -93
  10. iris_security_cli-0.1.9/iris_cli/models_cmd.py +138 -0
  11. iris_security_cli-0.1.9/iris_cli/preview.py +128 -0
  12. iris_security_cli-0.1.9/iris_cli/quickstart.py +423 -0
  13. iris_security_cli-0.1.9/iris_cli/sentinel.py +124 -0
  14. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/status_cmd.py +10 -0
  15. iris_security_cli-0.1.9/iris_cli/witness.py +123 -0
  16. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_security_cli.egg-info/PKG-INFO +5 -5
  17. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_security_cli.egg-info/SOURCES.txt +14 -1
  18. iris_security_cli-0.1.9/iris_security_cli.egg-info/requires.txt +8 -0
  19. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/pyproject.toml +6 -5
  20. iris_security_cli-0.1.9/tests/test_mcp_model_governance.py +35 -0
  21. iris_security_cli-0.1.9/tests/test_quickstart.py +95 -0
  22. iris_security_cli-0.1.9/tests/test_vocabulary.py +353 -0
  23. iris_security_cli-0.1.7/iris_cli/compliance_check_cmd.py +0 -127
  24. iris_security_cli-0.1.7/iris_security_cli.egg-info/requires.txt +0 -8
  25. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/README.md +0 -0
  26. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/__init__.py +0 -0
  27. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/action_plan.py +0 -0
  28. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/assess.py +0 -0
  29. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/cedar_parser.py +0 -0
  30. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/compiler_config.py +0 -0
  31. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/cost.py +0 -0
  32. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/dlp_cmd.py +0 -0
  33. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/drift.py +0 -0
  34. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/entitlements_cmd.py +0 -0
  35. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/evidence.py +0 -0
  36. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/explain.py +0 -0
  37. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/framework_suggest.py +0 -0
  38. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/framework_test.py +0 -0
  39. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/mcp_server.py +0 -0
  40. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/policy_cache.py +0 -0
  41. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/policy_diff.py +0 -0
  42. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/redteam.py +0 -0
  43. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/regulatory.py +0 -0
  44. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/scan_govern.py +0 -0
  45. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/scan_report.py +0 -0
  46. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/scm.py +0 -0
  47. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/users.py +0 -0
  48. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/vault.py +0 -0
  49. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_cli/watch.py +0 -0
  50. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_security_cli.egg-info/dependency_links.txt +0 -0
  51. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_security_cli.egg-info/entry_points.txt +0 -0
  52. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/iris_security_cli.egg-info/top_level.txt +0 -0
  53. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/setup.cfg +0 -0
  54. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/tests/test_entitlements_display.py +0 -0
  55. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/tests/test_evidence.py +0 -0
  56. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/tests/test_framework_suggest.py +0 -0
  57. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/tests/test_framework_test.py +0 -0
  58. {iris_security_cli-0.1.7 → iris_security_cli-0.1.9}/tests/test_policy_diff.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-cli
3
- Version: 0.1.7
4
- Summary: IRIS CLI — iris scan, iris register, iris policy, iris compliance
3
+ Version: 0.1.9
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
7
7
  Project-URL: Repository, 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.6
19
- Requires-Dist: iris-security-sdk>=0.1.7
18
+ Requires-Dist: iris-security-core>=0.1.7
19
+ Requires-Dist: iris-security-sdk>=0.1.9
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.0; extra == "scm"
24
+ Requires-Dist: iris-security-scm[all]>=0.1.1; extra == "scm"
25
25
 
26
26
  # iris-security-cli
27
27
 
@@ -0,0 +1,105 @@
1
+ """iris certify — prove compliance readiness to any regulatory framework."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import click
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+
13
+ from iris_core.entitlements import Entitlements, Feature
14
+ from iris_core.models.passport import AgentPassport
15
+
16
+ from iris_cli.framework_test import (
17
+ _build_result,
18
+ _framework_name,
19
+ _load_previous_score,
20
+ _render_json,
21
+ _render_markdown,
22
+ _render_table,
23
+ _save_result,
24
+ )
25
+
26
+ console = Console()
27
+
28
+
29
+ def _render_certification_header(
30
+ framework: str,
31
+ agent_name: str,
32
+ result,
33
+ ) -> None:
34
+ framework_label = _framework_name(framework)
35
+ if framework == "colorado-ai-act":
36
+ framework_label = "Colorado AI Act (SB 26-189)"
37
+
38
+ 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",
43
+ title=f"IRIS Certification — {framework}",
44
+ style="blue",
45
+ ))
46
+
47
+ evaluated = result.passed_controls + result.failed_controls + result.partial_controls
48
+ pct = result.score_percent
49
+ filled = int((pct / 100) * 20)
50
+ bar = "█" * filled + "░" * (20 - filled)
51
+ readiness = "READY" if pct >= 95 else result.readiness_level
52
+
53
+ console.print(f"\n[bold]CERTIFICATION RESULT: {readiness}[/bold]")
54
+ console.print(f"{bar} {result.passed_controls} / {result.total_controls} controls {pct}%")
55
+
56
+ if framework == "colorado-ai-act" and pct >= 95:
57
+ console.print(
58
+ f"\nThis agent is certification-ready for Colorado AI Act (SB 26-189).\n"
59
+ f"Effective date: January 1, 2027\n\n"
60
+ f"Evidence package: iris evidence report --agent {agent_name}\n"
61
+ f"Auditor note: Present this output alongside the Evidence Vault report "
62
+ f"to demonstrate compliance readiness."
63
+ )
64
+
65
+
66
+ @click.command("certify")
67
+ @click.option(
68
+ "--framework",
69
+ required=True,
70
+ type=click.Choice(sorted(["colorado-ai-act", "nist-ai-rmf", "fedramp-moderate", "hipaa", "soc2", "gdpr"])),
71
+ )
72
+ @click.option("--agent", "agent_name", required=True, help="Agent name under governance/agents")
73
+ @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:
75
+ """
76
+ Certify compliance readiness against a regulatory framework.
77
+
78
+ Alias: iris test
79
+ """
80
+ passport_path = Path.cwd() / "governance" / "agents" / agent_name / "passport.yaml"
81
+ if not passport_path.exists():
82
+ raise click.ClickException(f"Passport not found: {passport_path}")
83
+ passport = AgentPassport.from_yaml(passport_path.read_text())
84
+
85
+ ents = Entitlements()
86
+ has_pro = ents.has(Feature.CLI_TEST_FULL_REPORT)
87
+
88
+ result = _build_result(framework, agent_name, passport)
89
+ previous = _load_previous_score(agent_name, framework)
90
+ if previous is not None and has_pro:
91
+ result.progress_delta_percent = result.score_percent - previous
92
+
93
+ _save_result(result)
94
+
95
+ if output_format == "json":
96
+ click.echo(_render_json(result, has_pro))
97
+ elif output_format == "markdown":
98
+ click.echo(_render_markdown(result, has_pro))
99
+ else:
100
+ _render_certification_header(framework, agent_name, result)
101
+ _render_table(result, has_pro)
102
+
103
+
104
+ # Legacy alias: iris test → iris certify
105
+ test = certify_cmd
@@ -0,0 +1,180 @@
1
+ """iris compliance check — framework compliance with Pro preview."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import click
10
+ from rich.console import Console
11
+
12
+ from iris import AgentPassport
13
+ from iris_core.compliance.framework_check import run_framework_check
14
+ from iris_core.compliance.results import ComplianceCheckStatus
15
+ from iris_core.entitlements.display import build_pro_preview_box
16
+
17
+ from iris_cli.compliance_fix import offer_remediation, recheck_after_fixes
18
+ from iris_core.compliance.remediation import collect_fixable_issues
19
+
20
+ console = Console()
21
+
22
+
23
+ def _discover_passports(
24
+ gov_dir: Path,
25
+ agent: Optional[str],
26
+ ) -> list[AgentPassport]:
27
+ passports: list[AgentPassport] = []
28
+ if agent:
29
+ passport_file = gov_dir / agent / "passport.yaml"
30
+ if passport_file.exists():
31
+ passports.append(AgentPassport.from_yaml(passport_file.read_text()))
32
+ return passports
33
+
34
+ for passport_file in gov_dir.rglob("passport.yaml"):
35
+ try:
36
+ passports.append(AgentPassport.from_yaml(passport_file.read_text()))
37
+ except Exception:
38
+ continue
39
+ return passports
40
+
41
+
42
+ def _render_result(
43
+ passport: AgentPassport,
44
+ framework: str,
45
+ result,
46
+ ) -> bool:
47
+ """Print one agent's check result. Returns False if failures were found."""
48
+ if result.preview_only:
49
+ preview_lines = [
50
+ f"{rule.rule_id} {rule.severity} {rule.name} [{rule.status}]"
51
+ for rule in result.rule_results
52
+ ]
53
+ console.print(
54
+ build_pro_preview_box(
55
+ framework,
56
+ result.framework_name,
57
+ result.total_controls,
58
+ preview_lines,
59
+ )
60
+ )
61
+ return False
62
+
63
+ has_failures = any(rule.status == "FAIL" for rule in result.rule_results) or bool(
64
+ result.violations
65
+ )
66
+ status = "[bold green]PASS[/bold green]" if not has_failures else "[bold red]FAIL[/bold red]"
67
+ console.print(f"\nAgent: [cyan]{passport.name}[/cyan] — {status}")
68
+
69
+ if result.violations:
70
+ for violation in result.violations:
71
+ console.print(f" [red]✗[/red] [{violation.rule_id}] {violation.message}")
72
+ if violation.remediation:
73
+ console.print(f" [yellow]→[/yellow] {violation.remediation}")
74
+
75
+ shown_ids = {v.rule_id for v in result.violations}
76
+ for rule in result.rule_results:
77
+ if rule.rule_id in shown_ids:
78
+ continue
79
+ if rule.status == "PASS":
80
+ console.print(f" [green]✓[/green] [{rule.rule_id}] {rule.name}")
81
+ elif rule.status == "FAIL":
82
+ console.print(f" [red]✗[/red] [{rule.rule_id}] {rule.name}")
83
+ if rule.message:
84
+ console.print(f" {rule.message}")
85
+ if rule.remediation:
86
+ console.print(f" [yellow]→[/yellow] {rule.remediation}")
87
+
88
+ if not result.violations and not any(r.status == "FAIL" for r in result.rule_results):
89
+ console.print(f" [green]✓[/green] All {framework} rules satisfied")
90
+
91
+ for guidance in result.guidance:
92
+ if guidance.status == ComplianceCheckStatus.GUIDANCE:
93
+ console.print(f" [blue]ℹ[/blue] [{guidance.rule_id}] {guidance.message}")
94
+ if guidance.remediation:
95
+ console.print(f" [yellow]→[/yellow] {guidance.remediation}")
96
+ elif guidance.status == ComplianceCheckStatus.PRO_REQUIRED:
97
+ console.print(f" [yellow]⚠[/yellow] [{guidance.rule_id}] {guidance.message}")
98
+ if guidance.remediation:
99
+ console.print(f" [yellow]→[/yellow] {guidance.remediation}")
100
+
101
+ for note in result.check_notes:
102
+ console.print(f" [dim]ℹ {note}[/dim]")
103
+
104
+ return not has_failures
105
+
106
+
107
+ @click.command("check")
108
+ @click.option("--agent", default=None, help="Specific agent to check (or all)")
109
+ @click.option("--framework", "-f", default="colorado-ai-act")
110
+ @click.option("--dir", "governance_dir", type=click.Path(path_type=Path), default=None)
111
+ @click.option(
112
+ "--fix",
113
+ is_flag=True,
114
+ help="Apply all auto-fixable remediations without per-issue prompts",
115
+ )
116
+ @click.option(
117
+ "--yes",
118
+ "-y",
119
+ is_flag=True,
120
+ help="Apply auto-fixes without confirmation (use with --fix)",
121
+ )
122
+ @click.option(
123
+ "--no-fix",
124
+ is_flag=True,
125
+ help="Show remediations only — never offer to apply fixes",
126
+ )
127
+ def compliance_check_cmd(
128
+ agent: Optional[str],
129
+ framework: str,
130
+ governance_dir: Optional[Path],
131
+ fix: bool,
132
+ yes: bool,
133
+ no_fix: bool,
134
+ ) -> None:
135
+ """
136
+ Check an agent against a compliance framework.
137
+
138
+ Shows a detailed breakdown of which rules pass and fail,
139
+ with plain-English remediation guidance for each failure.
140
+ When run interactively, IRIS offers to apply safe fixes automatically.
141
+
142
+ Example:
143
+ iris compliance check --framework colorado-ai-act
144
+ iris compliance check --agent payment-agent --framework hipaa
145
+ iris compliance check --agent payment-agent --framework hipaa --fix -y
146
+ """
147
+ gov_dir = governance_dir or Path.cwd() / "governance" / "agents"
148
+ passports = _discover_passports(gov_dir, agent)
149
+ if not passports:
150
+ console.print("[yellow]No agent passports found.[/yellow]")
151
+ sys.exit(0)
152
+
153
+ all_pass = True
154
+ offer_fixes = bool(agent) or len(passports) == 1
155
+ for passport in passports:
156
+ result = run_framework_check(passport, framework)
157
+ agent_passed = _render_result(passport, framework, result)
158
+ if not agent_passed:
159
+ all_pass = False
160
+
161
+ if offer_fixes:
162
+ applied = offer_remediation(
163
+ passport,
164
+ framework,
165
+ result,
166
+ gov_dir,
167
+ auto_fix=fix,
168
+ yes=yes,
169
+ no_fix=no_fix,
170
+ )
171
+ recheck_after_fixes(passport, framework, gov_dir, applied)
172
+ elif not no_fix and not agent_passed and collect_fixable_issues(
173
+ passport, framework, result
174
+ ):
175
+ console.print(
176
+ f"[dim]Fixable issues for {passport.name}. "
177
+ f"Re-run with --agent {passport.name} to let IRIS apply fixes.[/dim]"
178
+ )
179
+
180
+ sys.exit(0 if all_pass else 1)
@@ -0,0 +1,202 @@
1
+ """Interactive compliance remediation — IRIS governance assistant."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import List, Optional
7
+
8
+ import click
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+
12
+ from iris import AgentPassport
13
+ from iris_core.compliance.framework_check import FrameworkCheckResult, run_framework_check
14
+ from iris_core.compliance.remediation import (
15
+ FixResult,
16
+ FixableIssue,
17
+ apply_fix,
18
+ collect_fixable_issues,
19
+ )
20
+
21
+ console = Console()
22
+
23
+
24
+ def _is_interactive() -> bool:
25
+ return click.get_text_stream("stdin").isatty()
26
+
27
+
28
+ def _print_fixable_summary(agent_name: str, issues: List[FixableIssue]) -> None:
29
+ lines = [
30
+ f"[bold]IRIS found {len(issues)} fixable issue"
31
+ f"{'s' if len(issues) != 1 else ''} for [cyan]{agent_name}[/cyan][/bold]\n"
32
+ ]
33
+ for issue in issues:
34
+ lines.append(f" [yellow]•[/yellow] [{issue.rule_id}] {issue.preview}")
35
+ lines.append(
36
+ "\n[dim]IRIS can apply safe passport and policy updates automatically. "
37
+ "Review each change before applying.[/dim]"
38
+ )
39
+ console.print(Panel("\n".join(lines), title="IRIS Governance Assistant", style="blue"))
40
+
41
+
42
+ def offer_remediation(
43
+ passport: AgentPassport,
44
+ framework: str,
45
+ result: FrameworkCheckResult,
46
+ governance_dir: Path,
47
+ *,
48
+ auto_fix: bool = False,
49
+ yes: bool = False,
50
+ no_fix: bool = False,
51
+ ) -> List[FixResult]:
52
+ """
53
+ Offer to apply fixable remediations after a compliance check.
54
+
55
+ Returns applied fix results (may be empty).
56
+ """
57
+ if no_fix or result.preview_only:
58
+ return []
59
+
60
+ issues = collect_fixable_issues(passport, framework, result)
61
+ if not issues:
62
+ return []
63
+
64
+ agent_dir = governance_dir / passport.name
65
+ passport_file = agent_dir / "passport.yaml"
66
+
67
+ if auto_fix or yes:
68
+ return _apply_all(issues, passport_file, framework, passport.name)
69
+
70
+ if not _is_interactive():
71
+ console.print(
72
+ "[dim]Fixable issues detected. Re-run with --fix to apply automatically.[/dim]"
73
+ )
74
+ return []
75
+
76
+ _print_fixable_summary(passport.name, issues)
77
+ if not click.confirm(
78
+ f"\nWould you like IRIS to fix these {len(issues)} issue"
79
+ f"{'s' if len(issues) != 1 else ''} for {passport.name}?",
80
+ default=True,
81
+ ):
82
+ console.print("[dim]Skipped automatic fixes. Apply remediations manually when ready.[/dim]")
83
+ return []
84
+
85
+ return _apply_interactive(issues, passport_file, framework, passport.name)
86
+
87
+
88
+ def _apply_all(
89
+ issues: List[FixableIssue],
90
+ passport_file: Path,
91
+ framework: str,
92
+ agent_name: str,
93
+ ) -> List[FixResult]:
94
+ results: List[FixResult] = []
95
+ for issue in issues:
96
+ result = apply_fix(issue, passport_file, framework)
97
+ results.append(result)
98
+ _print_fix_result(agent_name, issue, result)
99
+ if results:
100
+ console.print(
101
+ Panel(
102
+ f"[bold green]✓ IRIS applied {sum(1 for r in results if r.applied)} fix"
103
+ f"{'es' if sum(1 for r in results if r.applied) != 1 else ''}[/bold green]\n\n"
104
+ f"Re-run: [bold]iris compliance check --agent {agent_name} "
105
+ f"--framework {framework}[/bold]",
106
+ style="green",
107
+ )
108
+ )
109
+ return results
110
+
111
+
112
+ def _apply_interactive(
113
+ issues: List[FixableIssue],
114
+ passport_file: Path,
115
+ framework: str,
116
+ agent_name: str,
117
+ ) -> List[FixResult]:
118
+ results: List[FixResult] = []
119
+ apply_all = False
120
+
121
+ for issue in issues:
122
+ if apply_all:
123
+ result = apply_fix(issue, passport_file, framework)
124
+ results.append(result)
125
+ _print_fix_result(agent_name, issue, result)
126
+ continue
127
+
128
+ console.print(
129
+ f"\n[bold]IRIS can fix[/bold] [{issue.rule_id}] {issue.preview}\n"
130
+ f"[dim]{issue.message}[/dim]"
131
+ )
132
+ console.print("Apply this fix? ([yes]/no/skip/all)")
133
+ choice = click.prompt("", default="no", show_default=False).strip().lower()
134
+
135
+ if choice in ("yes", "y"):
136
+ result = apply_fix(issue, passport_file, framework)
137
+ results.append(result)
138
+ _print_fix_result(agent_name, issue, result)
139
+ elif choice == "all":
140
+ apply_all = True
141
+ result = apply_fix(issue, passport_file, framework)
142
+ results.append(result)
143
+ _print_fix_result(agent_name, issue, result)
144
+ elif choice == "skip":
145
+ console.print("[dim]Skipped.[/dim]")
146
+ else:
147
+ console.print("[dim]Not applied — fix manually when ready.[/dim]")
148
+
149
+ applied_count = sum(1 for r in results if r.applied)
150
+ if applied_count:
151
+ console.print(
152
+ Panel(
153
+ f"[bold green]✓ IRIS applied {applied_count} fix"
154
+ f"{'es' if applied_count != 1 else ''}[/bold green]\n\n"
155
+ f"Re-run: [bold]iris compliance check --agent {agent_name} "
156
+ f"--framework {framework}[/bold]",
157
+ style="green",
158
+ )
159
+ )
160
+ return results
161
+
162
+
163
+ def _print_fix_result(agent_name: str, issue: FixableIssue, result: FixResult) -> None:
164
+ if result.applied:
165
+ change_text = "\n".join(f" [dim]• {change}[/dim]" for change in result.changes)
166
+ console.print(f"[green]✓ Applied[/green] [{issue.rule_id}]\n{change_text}")
167
+ return
168
+ if result.skipped_reason:
169
+ console.print(
170
+ f"[yellow]⚠ Skipped[/yellow] [{issue.rule_id}]: {result.skipped_reason}"
171
+ )
172
+
173
+
174
+ def recheck_after_fixes(
175
+ passport: AgentPassport,
176
+ framework: str,
177
+ governance_dir: Path,
178
+ applied: List[FixResult],
179
+ ) -> Optional[FrameworkCheckResult]:
180
+ """Re-run check and show delta when fixes were applied."""
181
+ if not any(result.applied for result in applied):
182
+ return None
183
+
184
+ passport_file = governance_dir / passport.name / "passport.yaml"
185
+ if not passport_file.exists():
186
+ return None
187
+
188
+ updated = AgentPassport.from_yaml(passport_file.read_text())
189
+ result = run_framework_check(updated, framework)
190
+ remaining = collect_fixable_issues(updated, framework, result)
191
+ fail_count = sum(1 for rule in result.rule_results if rule.status == "FAIL")
192
+ fail_count += len(result.violations)
193
+
194
+ console.print(
195
+ Panel(
196
+ f"[bold]Re-check: {updated.name}[/bold]\n"
197
+ f"Remaining failures: {fail_count}\n"
198
+ f"Auto-fixable remaining: {len(remaining)}",
199
+ style="cyan",
200
+ )
201
+ )
202
+ return result
@@ -0,0 +1,177 @@
1
+ """iris declare — register an agent with optional interactive wizard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import click
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+
12
+ console = Console()
13
+
14
+ _DATA_CLASSIFICATIONS = {
15
+ "none": "internal",
16
+ "internal": "internal",
17
+ "pii": "pii",
18
+ "phi": "phi",
19
+ "financial": "confidential",
20
+ }
21
+
22
+
23
+ def _run_framework_suggest(agent_name: str) -> None:
24
+ console.print(f"[dim]Run: iris framework suggest --agent {agent_name}[/dim]")
25
+
26
+
27
+ def _write_agent_files(
28
+ name: str,
29
+ owner: str,
30
+ team: str,
31
+ env: tuple[str, ...],
32
+ high_risk: bool,
33
+ compliance_list: list[str],
34
+ output: Path,
35
+ description: str = "",
36
+ ) -> None:
37
+ from iris import IrisAgent, DataClassification, ComplianceTag
38
+
39
+ data_class = DataClassification.INTERNAL
40
+ agent = IrisAgent(
41
+ name=name,
42
+ owner=owner,
43
+ team=team,
44
+ compliance=compliance_list,
45
+ environments=list(env),
46
+ is_high_risk_ai=high_risk,
47
+ policy_dir=output,
48
+ )
49
+ if description:
50
+ agent.passport.description = description
51
+
52
+ output.mkdir(parents=True, exist_ok=True)
53
+ (output / "passport.yaml").write_text(agent.passport.to_yaml())
54
+
55
+ intent_template = f"""# Policy Intent — {name}
56
+
57
+ > Edit this file to describe what your agent is allowed to do.
58
+ > Then run: iris compile --agent {name}
59
+
60
+ ## What this agent does
61
+ {description or "[Describe the agent's purpose here]"}
62
+
63
+ ## What it is allowed to access
64
+ [List the tools, APIs, and data sources this agent needs]
65
+
66
+ ## What it must never do
67
+ [List explicit prohibitions]
68
+
69
+ ## Compliance notes
70
+ [Any compliance-specific context]
71
+ """
72
+ (output / "policy-intent.md").write_text(intent_template)
73
+
74
+
75
+ def _interactive_wizard(output_dir: Optional[Path]) -> None:
76
+ console.print(Panel("[bold]IRIS Agent Declaration[/bold]", style="blue"))
77
+
78
+ name = click.prompt("What is this agent's name?", type=str).strip()
79
+ description = click.prompt("What does this agent do? (plain English)", type=str).strip()
80
+ owner = click.prompt("Who owns it? (email)", type=str).strip()
81
+ team = click.prompt("Which team?", type=str).strip()
82
+ high_risk = click.confirm("Does it make consequential decisions?", default=False)
83
+
84
+ console.print("What data does it access?")
85
+ console.print(" 1. None 2. Internal only 3. PII 4. PHI 5. Financial")
86
+ data_choice = click.prompt("Choice", type=click.IntRange(1, 5), default=2)
87
+ data_keys = ["none", "internal", "pii", "phi", "financial"]
88
+ data_key = data_keys[data_choice - 1]
89
+
90
+ output = output_dir or (Path.cwd() / "governance" / "agents" / name)
91
+ compliance_list = ["colorado-ai-act"]
92
+
93
+ if not click.confirm(f"Write agent files to {output}?", default=True):
94
+ console.print("[yellow]Cancelled.[/yellow]")
95
+ return
96
+
97
+ _write_agent_files(
98
+ name=name,
99
+ owner=owner,
100
+ team=team,
101
+ env=("dev",),
102
+ high_risk=high_risk,
103
+ compliance_list=compliance_list,
104
+ output=output,
105
+ description=description,
106
+ )
107
+
108
+ console.print("\n[bold]Recommended compliance tags:[/bold]")
109
+ _run_framework_suggest(name)
110
+
111
+ console.print(Panel(
112
+ f"[bold green]✓ Agent declared[/bold green]\n\n"
113
+ f"Name: [cyan]{name}[/cyan]\n"
114
+ f"Data: {_DATA_CLASSIFICATIONS.get(data_key, 'internal')}\n"
115
+ f"Passport: {output / 'passport.yaml'}\n\n"
116
+ f"Next: Edit [bold]{output / 'policy-intent.md'}[/bold]\n"
117
+ f"Then: [bold]iris compile --agent {name}[/bold]",
118
+ style="green",
119
+ ))
120
+
121
+
122
+ @click.command("declare")
123
+ @click.option("--name", default=None, help="Agent identifier (kebab-case)")
124
+ @click.option("--owner", default=None, help="Owner email address")
125
+ @click.option("--team", default=None, help="Team or squad name")
126
+ @click.option("--env", multiple=True, default=None, help="Environments (repeatable)")
127
+ @click.option("--high-risk", is_flag=True, default=False, help="Flag as high-risk AI")
128
+ @click.option("--compliance", "-c", multiple=True, help="Compliance frameworks to tag")
129
+ @click.option("--dir", "output_dir", type=click.Path(path_type=Path), default=None)
130
+ def declare(
131
+ name: Optional[str],
132
+ owner: Optional[str],
133
+ team: Optional[str],
134
+ env: Optional[tuple[str, ...]],
135
+ high_risk: bool,
136
+ compliance: tuple[str, ...],
137
+ output_dir: Optional[Path],
138
+ ) -> None:
139
+ """
140
+ Declare what an agent is allowed to do — creates passport.yaml and intent template.
141
+
142
+ Run without flags for the interactive declaration wizard.
143
+
144
+ Example:
145
+ iris declare
146
+ iris declare --name payment-agent --owner alice@co.com --team platform
147
+ """
148
+ if name is None and owner is None and team is None:
149
+ _interactive_wizard(output_dir)
150
+ return
151
+
152
+ resolved_name = name or click.prompt("Agent name")
153
+ resolved_owner = owner or click.prompt("Owner email")
154
+ resolved_team = team or click.prompt("Team name")
155
+ compliance_list = list(compliance) or ["colorado-ai-act"]
156
+ env_list = tuple(env) if env else ("dev",)
157
+ output = output_dir or (Path.cwd() / "governance" / "agents" / resolved_name)
158
+
159
+ _write_agent_files(
160
+ name=resolved_name,
161
+ owner=resolved_owner,
162
+ team=resolved_team,
163
+ env=env_list,
164
+ high_risk=high_risk,
165
+ compliance_list=compliance_list,
166
+ output=output,
167
+ )
168
+
169
+ console.print(Panel(
170
+ f"[bold green]✓ Agent declared[/bold green]\n\n"
171
+ f"Name: [cyan]{resolved_name}[/cyan]\n"
172
+ f"Passport: {output / 'passport.yaml'}\n"
173
+ f"Intent template: {output / 'policy-intent.md'}\n\n"
174
+ f"Next step: Edit [bold]{output / 'policy-intent.md'}[/bold]\n"
175
+ f"Then run: [bold]iris compile --agent {resolved_name}[/bold]",
176
+ style="green",
177
+ ))