@hoanghainh/komagu 2.2.1 → 2.4.0

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 (44) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/assess/lsa/CHANGELOG.md +15 -0
  3. package/assess/lsa/scripts/attack_chains.py +573 -0
  4. package/assess/lsa/scripts/baseline.py +14 -0
  5. package/assess/lsa/scripts/detect_stack.py +2 -1
  6. package/assess/lsa/scripts/findings_to_sarif.py +11 -0
  7. package/assess/lsa/scripts/forecast.py +544 -0
  8. package/assess/lsa/scripts/lsa_common.py +1 -5
  9. package/assess/lsa/scripts/merge_findings.py +14 -0
  10. package/assess/lsa/scripts/redaction.py +118 -0
  11. package/assess/lsa/scripts/render_report.py +86 -0
  12. package/assess/lsa/tests/test_attack_chains.py +292 -0
  13. package/assess/lsa/tests/test_forecast.py +160 -0
  14. package/assess/lsa/tests/test_modern_stacks.py +13 -0
  15. package/contracts/schemas/api-inventory-diff.schema.json +213 -0
  16. package/contracts/schemas/attack-chain.schema.json +181 -0
  17. package/contracts/schemas/bas-scenario.schema.json +217 -0
  18. package/contracts/schemas/evidence-manifest.schema.json +117 -0
  19. package/contracts/schemas/finding.schema.json +7 -0
  20. package/contracts/schemas/forecast.schema.json +172 -0
  21. package/governance/scs/__init__.py +21 -0
  22. package/governance/scs/mapping.py +133 -0
  23. package/governance/scs/pack.py +354 -0
  24. package/operate/ai/context.py +22 -2
  25. package/operate/cli/__init__.py +1 -1
  26. package/operate/cli/commands/evidence.py +138 -0
  27. package/operate/cli/commands/inventory.py +187 -0
  28. package/operate/cli/commands/simulate.py +175 -58
  29. package/operate/cli/main.py +7 -3
  30. package/operate/inventory/__init__.py +22 -0
  31. package/operate/inventory/api_inventory.py +566 -0
  32. package/operate/simulate/__init__.py +16 -0
  33. package/operate/simulate/engine.py +370 -0
  34. package/operate/simulate/loader.py +158 -0
  35. package/operate/simulate/scenarios/idc-frontier-ransomware.json +47 -0
  36. package/operate/simulate/scenarios/metabase-cve-2026-72898.json +84 -0
  37. package/operate/simulate/scenarios/times-car-breach.json +74 -0
  38. package/operate/simulate/scenarios.py +52 -0
  39. package/operate/tests/test_ai_redaction.py +181 -0
  40. package/operate/tests/test_api_inventory.py +239 -0
  41. package/operate/tests/test_declarative_scenarios.py +169 -0
  42. package/operate/tests/test_evidence_pack.py +128 -0
  43. package/operate/tests/test_headless_ai.py +16 -1
  44. package/package.json +1 -1
@@ -0,0 +1,354 @@
1
+ #!/usr/bin/env python3
2
+ """METI SCS Evidence Pack Bundler and Gap Report Generator (#271).
3
+
4
+ Assembles technical audit artifacts into a timestamped, cryptographically
5
+ hashed ZIP archive with evidence-manifest.json and bilingual gap reports.
6
+
7
+ Standard library only (Python 3.8+) per ADR-0006.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import json
14
+ import os
15
+ import shutil
16
+ import time
17
+ import zipfile
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Optional, Tuple, Union
20
+
21
+ from governance.scs.mapping import (
22
+ LEGAL_DISCLAIMER_EN,
23
+ LEGAL_DISCLAIMER_JA,
24
+ SCS_CONTROLS,
25
+ )
26
+
27
+
28
+ def compute_file_sha256(path: Path) -> str:
29
+ """Compute standard SHA-256 hex digest of a file."""
30
+ h = hashlib.sha256()
31
+ with open(path, "rb") as f:
32
+ while chunk := f.read(65536):
33
+ h.update(chunk)
34
+ return h.hexdigest()
35
+
36
+
37
+ class EvidencePackBuilder:
38
+ """Evaluates audit directories and packages METI SCS evidence."""
39
+
40
+ def __init__(
41
+ self,
42
+ audit_dirs: List[Union[str, Path]],
43
+ project_name: str = "Komagu-Protected-Project",
44
+ target_system: str = "Production-Environment",
45
+ ) -> None:
46
+ self.audit_dirs = [Path(d).resolve() for d in audit_dirs]
47
+ self.project_name = project_name
48
+ self.target_system = target_system
49
+
50
+ def discover_artifacts(self) -> Dict[str, Path]:
51
+ """Discover available technical evidence artifacts across provided directories."""
52
+ discovered: Dict[str, Path] = {}
53
+ for d in self.audit_dirs:
54
+ if not d.is_dir():
55
+ continue
56
+ for root, _, files in os.walk(d):
57
+ for f in files:
58
+ fp = Path(root) / f
59
+ # Key by filename for mapping match
60
+ if f not in discovered:
61
+ discovered[f] = fp
62
+ return discovered
63
+
64
+ def evaluate_controls(self, artifacts: Dict[str, Path]) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
65
+ """Map discovered artifacts against METI SCS controls and compute readiness rating."""
66
+ evaluated_controls: List[Dict[str, Any]] = []
67
+ covered_count = 0
68
+ partial_count = 0
69
+
70
+ for ctrl in SCS_CONTROLS:
71
+ matched_files: List[str] = []
72
+ for exp in ctrl["expected_artifacts"]:
73
+ if exp in artifacts:
74
+ matched_files.append(exp)
75
+
76
+ if ctrl["control_id"] == "SCS-OG-01":
77
+ # Organizational governance requires manual policy attachment
78
+ status = "partially_covered" if matched_files else "out_of_scope_organizational"
79
+ elif matched_files:
80
+ status = "covered"
81
+ else:
82
+ status = "missing_evidence"
83
+
84
+ if status == "covered":
85
+ covered_count += 1
86
+ elif status == "partially_covered":
87
+ partial_count += 1
88
+
89
+ entry = dict(ctrl)
90
+ entry["status"] = status
91
+ entry["evidence_files"] = matched_files
92
+ evaluated_controls.append(entry)
93
+
94
+ total = len(SCS_CONTROLS)
95
+ compliance_pct = round(((covered_count + (0.5 * partial_count)) / total) * 100.0, 1)
96
+
97
+ if compliance_pct >= 85.0 and any(c["tier"] == "★4" and c["status"] == "covered" for c in evaluated_controls):
98
+ rating = "SCS-★4-READY"
99
+ elif compliance_pct >= 60.0:
100
+ rating = "SCS-★3-READY"
101
+ elif compliance_pct > 0:
102
+ rating = "GAP-IDENTIFIED"
103
+ else:
104
+ rating = "INSUFFICIENT-EVIDENCE"
105
+
106
+ readiness = {
107
+ "rating": rating,
108
+ "total_controls": total,
109
+ "covered_controls": covered_count,
110
+ "partial_controls": partial_count,
111
+ "compliance_percentage": compliance_pct,
112
+ }
113
+
114
+ return evaluated_controls, readiness
115
+
116
+ def generate_manifest(
117
+ self,
118
+ evaluated_controls: List[Dict[str, Any]],
119
+ readiness: Dict[str, Any],
120
+ artifacts: Dict[str, Path],
121
+ ) -> Dict[str, Any]:
122
+ """Generate evidence-manifest.json conforming to evidence-manifest.schema.json."""
123
+ bundled_records: List[Dict[str, Any]] = []
124
+
125
+ # Collect artifacts referenced in controls
126
+ used_files = set()
127
+ for ctrl in evaluated_controls:
128
+ for ef in ctrl["evidence_files"]:
129
+ used_files.add(ef)
130
+
131
+ for fname in sorted(used_files):
132
+ fp = artifacts.get(fname)
133
+ if fp and fp.is_file():
134
+ bundled_records.append({
135
+ "filename": fname,
136
+ "artifact_type": fp.suffix.lstrip(".") or "txt",
137
+ "sha256": compute_file_sha256(fp),
138
+ "size_bytes": fp.stat().st_size,
139
+ "description": f"Audit artifact: {fname}",
140
+ })
141
+
142
+ manifest = {
143
+ "$schema": "https://raw.githubusercontent.com/hoanghainh1188/komagu/main/contracts/schemas/evidence-manifest.schema.json",
144
+ "manifest_version": "1.0.0",
145
+ "project_name": self.project_name,
146
+ "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
147
+ "target_system": self.target_system,
148
+ "frameworks": [
149
+ "METI Supply Chain Security Evaluation Scheme (SCS ★3/★4)",
150
+ "NIST Cybersecurity Framework (NIST CSF 2.0)",
151
+ "Japan Active Cyber Defense Act (能動的サイバー防御 2025)",
152
+ ],
153
+ "overall_readiness": readiness,
154
+ "controls": evaluated_controls,
155
+ "bundled_artifacts": bundled_records,
156
+ "disclaimer": LEGAL_DISCLAIMER_JA + " " + LEGAL_DISCLAIMER_EN,
157
+ }
158
+
159
+ return manifest
160
+
161
+ def render_gap_report_ja(self, manifest: Dict[str, Any]) -> str:
162
+ """Render Japanese METI SCS Gap Analysis Report (gap-report.ja.md)."""
163
+ r = manifest["overall_readiness"]
164
+ lines = [
165
+ "# 経済産業省 サプライチェーン・サイバーセキュリティ (SCS ★3/★4) 適合性ギャップ分析レポート",
166
+ "",
167
+ "> **対象プロジェクト / システム:** `" + manifest["project_name"] + " (" + manifest["target_system"] + ")` ",
168
+ "> **評価実施日時:** `" + manifest["generated_at"] + "` ",
169
+ "> **総合適合性ステータス:** **`" + r["rating"] + "`** (適合率: **" + str(r["compliance_percentage"]) + "%**) ",
170
+ "",
171
+ "## 1. 概要及び評価結果サマリー",
172
+ "",
173
+ "本レポートは、2026年10月施行の**能動的サイバー防御法**および経済産業省**サプライチェーン・サイバーセキュリティ評価制度(SCS ★3/★4、NIST CSF 2.0準拠)**に基づき、"
174
+ "Komaguが検出・収集したセキュリティ監査成果物を自動照合した結果です。",
175
+ "",
176
+ "| 評価項目 | 結果 / 指標 |",
177
+ "|---|---|",
178
+ f"| **評価対象統制数** | {r['total_controls']} 項目 |",
179
+ f"| **技術的エビデンス充足 (Covered)** | {r['covered_controls']} 項目 |",
180
+ f"| **部分的充足 / 組織的対応要 (Partial)** | {r['partial_controls']} 項目 |",
181
+ f"| **総合適合率** | **{r['compliance_percentage']}%** |",
182
+ f"| **判定ランク** | **{r['rating']}** |",
183
+ "",
184
+ "## 2. METI SCS / NIST CSF 2.0 統制項目別 照合結果マトリクス",
185
+ "",
186
+ "| 統制ID | 統制名 / カテゴリ | 要求水準 | NIST CSF 2.0 | ステータス | 添付エビデンス |",
187
+ "|---|---|---|---|---|---|",
188
+ ]
189
+
190
+ status_ja = {
191
+ "covered": "✅ 適合 (Covered)",
192
+ "partially_covered": "⚠️ 部分的充足 (Partial)",
193
+ "out_of_scope_organizational": "ℹ️ 組織的対応 (Organizational)",
194
+ "missing_evidence": "❌ 証跡不足 (Missing)",
195
+ }
196
+
197
+ for c in manifest["controls"]:
198
+ st = status_ja.get(c["status"], c["status"])
199
+ ev_str = ", ".join(f"`{f}`" for f in c["evidence_files"]) if c["evidence_files"] else "*(なし)*"
200
+ csf = ", ".join(c["nist_csf_v2"])
201
+ lines.append(f"| **{c['control_id']}** | {c['name_ja']} | `{c['tier']}` | `{csf}` | {st} | {ev_str} |")
202
+
203
+ lines.extend([
204
+ "",
205
+ "## 3. 残存ギャップおよび是正アクション (Action Items)",
206
+ "",
207
+ ])
208
+
209
+ gap_found = False
210
+ for c in manifest["controls"]:
211
+ if c["status"] != "covered":
212
+ gap_found = True
213
+ lines.append(f"### 【{c['control_id']}】{c['name_ja']} ({c['tier']})")
214
+ lines.append(f"- **NIST CSF 2.0 カテゴリ:** `{', '.join(c['nist_csf_v2'])}`")
215
+ lines.append(f"- **現在の状態:** {status_ja.get(c['status'], c['status'])}")
216
+ lines.append(f"- **推奨是正アクション:** {c.get('action_required_ja', '社内規程の整備')}")
217
+ lines.append("")
218
+
219
+ if not gap_found:
220
+ lines.append("🎉 すべての統制項目に対して技術的エビデンスが充足しています。元請企業への提出が可能です。\n")
221
+
222
+ lines.extend([
223
+ "## 4. 添付エビデンスファイル一覧 (SHA-256 チェックサム)",
224
+ "",
225
+ "| ファイル名 | 種別 | SHA-256 チェックサム | サイズ |",
226
+ "|---|---|---|---|",
227
+ ])
228
+
229
+ for a in manifest["bundled_artifacts"]:
230
+ lines.append(f"| `{a['filename']}` | `{a['artifact_type']}` | `{a['sha256'][:16]}...` | {a['size_bytes']} bytes |")
231
+
232
+ lines.extend([
233
+ "",
234
+ "---",
235
+ "### 法的免責事項 (Legal Disclaimer)",
236
+ LEGAL_DISCLAIMER_JA,
237
+ "",
238
+ "*Report generated autonomously by Komagu Security-First Modernization Platform.*",
239
+ ])
240
+
241
+ return "\n".join(lines) + "\n"
242
+
243
+ def render_gap_report_en(self, manifest: Dict[str, Any]) -> str:
244
+ """Render English METI SCS Gap Analysis Report (gap-report.md)."""
245
+ r = manifest["overall_readiness"]
246
+ lines = [
247
+ "# METI Supply Chain Security (SCS ★3/★4) & NIST CSF 2.0 Gap Analysis Report",
248
+ "",
249
+ "> **Target Project / System:** `" + manifest["project_name"] + " (" + manifest["target_system"] + ")` ",
250
+ "> **Evaluation Timestamp:** `" + manifest["generated_at"] + "` ",
251
+ "> **Readiness Verdict:** **`" + r["rating"] + "`** (Compliance Score: **" + str(r["compliance_percentage"]) + "%**) ",
252
+ "",
253
+ "## 1. Executive Summary",
254
+ "",
255
+ "This report maps automated Komagu technical security audit artifacts against the Japanese Ministry of Economy, "
256
+ "Trade and Industry (METI) Supply Chain Security Evaluation Scheme (SCS ★3/★4) and NIST Cybersecurity Framework (CSF 2.0).",
257
+ "",
258
+ "| Metric | Value |",
259
+ "|---|---|",
260
+ f"| **Total Controls Evaluated** | {r['total_controls']} |",
261
+ f"| **Technically Covered** | {r['covered_controls']} |",
262
+ f"| **Partially Covered / Organizational** | {r['partial_controls']} |",
263
+ f"| **Overall Compliance Score** | **{r['compliance_percentage']}%** |",
264
+ f"| **Readiness Status** | **{r['rating']}** |",
265
+ "",
266
+ "## 2. Control Evaluation Matrix",
267
+ "",
268
+ "| Control ID | Name | Tier | NIST CSF 2.0 | Status | Evidence Artifacts |",
269
+ "|---|---|---|---|---|---|",
270
+ ]
271
+
272
+ status_en = {
273
+ "covered": "PASSED (Covered)",
274
+ "partially_covered": "PARTIAL (Action Req)",
275
+ "out_of_scope_organizational": "ORGANIZATIONAL",
276
+ "missing_evidence": "MISSING",
277
+ }
278
+
279
+ for c in manifest["controls"]:
280
+ st = status_en.get(c["status"], c["status"])
281
+ ev_str = ", ".join(f"`{f}`" for f in c["evidence_files"]) if c["evidence_files"] else "*(none)*"
282
+ csf = ", ".join(c["nist_csf_v2"])
283
+ lines.append(f"| **{c['control_id']}** | {c['name_en']} | `{c['tier']}` | `{csf}` | `{st}` | {ev_str} |")
284
+
285
+ lines.extend([
286
+ "",
287
+ "## 3. Gap Analysis & Remediation Action Plan",
288
+ "",
289
+ ])
290
+
291
+ for c in manifest["controls"]:
292
+ if c["status"] != "covered":
293
+ lines.append(f"### [{c['control_id']}] {c['name_en']} ({c['tier']})")
294
+ lines.append(f"- **NIST CSF 2.0 Category:** `{', '.join(c['nist_csf_v2'])}`")
295
+ lines.append(f"- **Current Status:** `{status_en.get(c['status'], c['status'])}`")
296
+ lines.append(f"- **Recommended Action:** {c.get('action_required_en', 'Document internal policy.')}")
297
+ lines.append("")
298
+
299
+ lines.extend([
300
+ "## 4. Bundled Artifact Manifest & Cryptographic Hashes",
301
+ "",
302
+ "| Filename | Type | SHA-256 Digest | Size (Bytes) |",
303
+ "|---|---|---|---|",
304
+ ])
305
+
306
+ for a in manifest["bundled_artifacts"]:
307
+ lines.append(f"| `{a['filename']}` | `{a['artifact_type']}` | `{a['sha256']}` | {a['size_bytes']} |")
308
+
309
+ lines.extend([
310
+ "",
311
+ "---",
312
+ "### Disclaimer",
313
+ LEGAL_DISCLAIMER_EN,
314
+ "",
315
+ "*Report generated autonomously by Komagu Security-First Modernization Platform.*",
316
+ ])
317
+
318
+ return "\n".join(lines) + "\n"
319
+
320
+ def build_pack(self, output_dir: Union[str, Path]) -> Tuple[Path, Path, Path, Path]:
321
+ """Build evidence pack ZIP, manifest JSON, and bilingual reports.
322
+
323
+ Returns:
324
+ (zip_path, manifest_path, gap_report_ja_path, gap_report_en_path)
325
+ """
326
+ out_p = Path(output_dir).resolve()
327
+ out_p.mkdir(parents=True, exist_ok=True)
328
+
329
+ artifacts = self.discover_artifacts()
330
+ evaluated_controls, readiness = self.evaluate_controls(artifacts)
331
+ manifest = self.generate_manifest(evaluated_controls, readiness, artifacts)
332
+
333
+ manifest_path = out_p / "evidence-manifest.json"
334
+ manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8")
335
+
336
+ gap_report_ja_path = out_p / "gap-report.ja.md"
337
+ gap_report_ja_path.write_text(self.render_gap_report_ja(manifest), encoding="utf-8")
338
+
339
+ gap_report_en_path = out_p / "gap-report.md"
340
+ gap_report_en_path.write_text(self.render_gap_report_en(manifest), encoding="utf-8")
341
+
342
+ # Create evidence-pack.zip
343
+ zip_path = out_p / "evidence-pack.zip"
344
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
345
+ zf.write(manifest_path, arcname="evidence-manifest.json")
346
+ zf.write(gap_report_ja_path, arcname="gap-report.ja.md")
347
+ zf.write(gap_report_en_path, arcname="gap-report.md")
348
+
349
+ for item in manifest["bundled_artifacts"]:
350
+ fname = item["filename"]
351
+ if fname in artifacts and artifacts[fname].is_file():
352
+ zf.write(artifacts[fname], arcname=f"artifacts/{fname}")
353
+
354
+ return zip_path, manifest_path, gap_report_ja_path, gap_report_en_path
@@ -5,18 +5,38 @@ Standard-library only (Python 3.8+ compatible).
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import importlib.util
8
9
  import re
9
10
  from pathlib import Path
11
+ from types import ModuleType
10
12
  from typing import Any, Dict, List, Optional
11
13
 
14
+ LSA_REDACTION = Path(__file__).resolve().parents[2] / "assess" / "lsa" / "scripts" / "redaction.py"
15
+
16
+
17
+ def _load_redaction() -> ModuleType:
18
+ """Load the LSA kit's stdlib-only redaction module, so Tier 1 and the agents share one set of patterns."""
19
+ spec = importlib.util.spec_from_file_location("komagu_lsa_redaction", LSA_REDACTION)
20
+ if spec is None or spec.loader is None:
21
+ raise ImportError(f"cannot load LSA redaction from {LSA_REDACTION}")
22
+ module = importlib.util.module_from_spec(spec)
23
+ spec.loader.exec_module(module)
24
+ return module
25
+
26
+
27
+ _redact_lines = _load_redaction().redact_lines
28
+
12
29
 
13
30
  def _read_snippet(target_dir: Path, rel_file: str, line: int, radius: int = 15) -> str:
14
- """Read a window of code around target line from the audited repository."""
31
+ """Read a window of code around target line from the audited repository, with secrets redacted.
32
+
33
+ Every code excerpt sent to the model goes through here (#350).
34
+ """
15
35
  full_path = target_dir / rel_file
16
36
  if not full_path.is_file():
17
37
  return f"[File not found: {rel_file}]"
18
38
  try:
19
- lines = full_path.read_text(encoding="utf-8", errors="replace").splitlines()
39
+ lines = _redact_lines(full_path.read_text(encoding="utf-8", errors="replace").splitlines())
20
40
  start = max(0, line - radius - 1)
21
41
  end = min(len(lines), line + radius)
22
42
  numbered = [f"{i + 1:4d} | {lines[i]}" for i in range(start, end)]
@@ -2,4 +2,4 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "2.2.1" # x-release-please-version
5
+ __version__ = "2.4.0" # x-release-please-version
@@ -0,0 +1,138 @@
1
+ """Command: komagu evidence - METI SCS ★3/★4 & NIST CSF 2.0 Evidence Pack Bundler (#271)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import List
9
+
10
+ from governance.scs.pack import EvidencePackBuilder
11
+ from operate.cli.ui import (
12
+ BOLD,
13
+ CYAN,
14
+ GREEN,
15
+ RED,
16
+ YELLOW,
17
+ color,
18
+ print_summary_table,
19
+ step,
20
+ step_fail,
21
+ step_info,
22
+ step_ok,
23
+ step_warn,
24
+ )
25
+
26
+
27
+ def register_subcommand(subparsers: argparse._SubParsersAction) -> None:
28
+ """Register the evidence subcommand parser."""
29
+ parser = subparsers.add_parser(
30
+ "evidence",
31
+ help="Generate METI Supply Chain Security (SCS ★3/★4) evidence pack and gap report",
32
+ description="Bundles technical audit artifacts into a timestamped, cryptographically hashed evidence pack (ZIP + JSON manifest) with Japanese/English gap reports.",
33
+ formatter_class=argparse.RawDescriptionHelpFormatter,
34
+ epilog="""
35
+ Examples:
36
+ # Generate METI SCS evidence pack from default audit output directory (OUT/)
37
+ komagu evidence --audit-dir OUT --output-dir ./meti-evidence
38
+
39
+ # Generate evidence pack specifying project and system names
40
+ komagu evidence \
41
+ --audit-dir OUT \
42
+ --project "FinTech Core Banking" \
43
+ --system "Staging API Cluster" \
44
+ --output-dir ./audit-pack
45
+
46
+ # Enforce minimum readiness rating (e.g. fail CI if SCS-★3-READY is not met)
47
+ komagu evidence --audit-dir OUT --require-tier 3
48
+ """,
49
+ )
50
+ parser.add_argument(
51
+ "--audit-dir",
52
+ "-d",
53
+ action="append",
54
+ dest="audit_dirs",
55
+ metavar="DIR",
56
+ help="Path to directory containing audit outputs (e.g. OUT/, findings.sarif, sbom.json). Repeatable.",
57
+ )
58
+ parser.add_argument(
59
+ "--output-dir",
60
+ "-o",
61
+ default="./evidence-pack",
62
+ help="Directory to save evidence-pack.zip, evidence-manifest.json, and gap reports (default: ./evidence-pack)",
63
+ )
64
+ parser.add_argument(
65
+ "--project",
66
+ default="Target Modernization System",
67
+ help="Project or organization name (default: Target Modernization System)",
68
+ )
69
+ parser.add_argument(
70
+ "--system",
71
+ default="Production Architecture",
72
+ help="Target system or environment name (default: Production Architecture)",
73
+ )
74
+ parser.add_argument(
75
+ "--require-tier",
76
+ type=int,
77
+ choices=[3, 4],
78
+ help="Fail with exit code 1 if evaluated readiness does not meet the specified star tier (3 or 4)",
79
+ )
80
+ parser.set_defaults(func=execute_evidence)
81
+
82
+
83
+ def execute_evidence(args: argparse.Namespace) -> int:
84
+ """Execute evidence pack generation and gap analysis."""
85
+ audit_dirs = args.audit_dirs or ["OUT", "audit-out", "delivery/pilot/audit-out", "."]
86
+
87
+ step(f"Assembling METI SCS Evidence Pack for {color(args.project, BOLD)} ({args.system})")
88
+ step_info(f"Scanning audit source directories: {', '.join(audit_dirs)}")
89
+
90
+ builder = EvidencePackBuilder(
91
+ audit_dirs=audit_dirs,
92
+ project_name=args.project,
93
+ target_system=args.system,
94
+ )
95
+
96
+ artifacts = builder.discover_artifacts()
97
+ step_info(f"Discovered {len(artifacts)} audit artifacts across directories")
98
+
99
+ evaluated_controls, readiness = builder.evaluate_controls(artifacts)
100
+ rating = readiness["rating"]
101
+ score = readiness["compliance_percentage"]
102
+
103
+ # Build ZIP and reports
104
+ zip_path, manifest_path, gap_ja, gap_en = builder.build_pack(args.output_dir)
105
+
106
+ step_ok(f"Generated Evidence Manifest: {manifest_path}")
107
+ step_ok(f"Generated Japanese Gap Report: {gap_ja}")
108
+ step_ok(f"Generated English Gap Report: {gap_en}")
109
+ step_ok(f"Packaged Signed ZIP Archive: {zip_path}")
110
+
111
+ # Display Executive Summary Table
112
+ rating_color = GREEN if "READY" in rating else (YELLOW if rating == "GAP-IDENTIFIED" else RED)
113
+ summary_rows = [
114
+ ("Project Name", args.project),
115
+ ("Target System", args.system),
116
+ ("Total SCS Controls Evaluated", str(readiness["total_controls"])),
117
+ ("Technically Covered Controls", color(str(readiness["covered_controls"]), GREEN, BOLD)),
118
+ ("Partial / Organizational", color(str(readiness["partial_controls"]), YELLOW)),
119
+ ("Compliance Score", color(f"{score}%", rating_color, BOLD)),
120
+ ("METI SCS Readiness Rating", color(rating, rating_color, BOLD)),
121
+ ]
122
+
123
+ print_summary_table(
124
+ title="METI Supply Chain Security (SCS ★3/★4) Assessment Summary",
125
+ rows=summary_rows,
126
+ footer="Evidence pack ready for submission to prime contractor / accredited auditor."
127
+ )
128
+
129
+ # Gating enforcement if requested
130
+ if args.require_tier:
131
+ if args.require_tier == 4 and rating != "SCS-★4-READY":
132
+ step_fail(f"Quality Gate Failed: Required SCS-★4-READY, evaluated as {rating} ({score}%)")
133
+ return 1
134
+ elif args.require_tier == 3 and rating not in ("SCS-★3-READY", "SCS-★4-READY"):
135
+ step_fail(f"Quality Gate Failed: Required SCS-★3-READY, evaluated as {rating} ({score}%)")
136
+ return 1
137
+
138
+ return 0