diff --git a/README.md b/README.md index 675ab5b4..1b5f3de2 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ ## Table of Contents +- [What's new (2026-06-19) — Compliance Control Report (SOC2 / ISO 27001)](#whats-new-2026-06-19--compliance-control-report-soc2--iso-27001) - [What's new (2026-06-19) — Agent Trajectory Evaluation](#whats-new-2026-06-19--agent-trajectory-evaluation) - [What's new (2026-06-19) — Approval Testing (Golden-Master Baselines)](#whats-new-2026-06-19--approval-testing-golden-master-baselines) - [What's new (2026-06-19) — Network Egress Allowlist Guard](#whats-new-2026-06-19--network-egress-allowlist-guard) @@ -89,6 +90,12 @@ --- +## What's new (2026-06-19) — Compliance Control Report (SOC2 / ISO 27001) + +Map governance evidence to named controls. Full reference: [`docs/source/Eng/doc/new_features/v37_features_doc.rst`](docs/source/Eng/doc/new_features/v37_features_doc.rst). + +- **`build_compliance_report`** (`AC_compliance_report`, `ac_compliance_report`): the framework already ships the controls an auditor cares about — egress allowlist, JIT credential leases, maker-checker approval, secrets scanner, audit logging, CycloneDX SBOM. This maps a flat `evidence` mapping to SOC2 (CC6.1/CC6.3/CC6.8/CC7.3/CC8.1) and ISO 27001 (A.5.23/A.8.16/A.8.30) controls, each marked `satisfied`/`gap`/`not_assessed`, and renders JSON or a standalone HTML table. The capstone of the governance set — a reporting aid, not a certification. + ## What's new (2026-06-19) — Agent Trajectory Evaluation Score an agent run against a rubric. Full reference: [`docs/source/Eng/doc/new_features/v36_features_doc.rst`](docs/source/Eng/doc/new_features/v36_features_doc.rst). diff --git a/README/README_zh-CN.md b/README/README_zh-CN.md index 63c5f6be..f3937533 100644 --- a/README/README_zh-CN.md +++ b/README/README_zh-CN.md @@ -12,6 +12,7 @@ ## 目录 +- [本次更新 (2026-06-19) — 合规控制报告(SOC2 / ISO 27001)](#本次更新-2026-06-19--合规控制报告soc2--iso-27001) - [本次更新 (2026-06-19) — Agent 轨迹评估](#本次更新-2026-06-19--agent-轨迹评估) - [本次更新 (2026-06-19) — 核准式测试(Golden-Master 基准)](#本次更新-2026-06-19--核准式测试golden-master-基准) - [本次更新 (2026-06-19) — 网络出口允许清单守卫](#本次更新-2026-06-19--网络出口允许清单守卫) @@ -88,6 +89,12 @@ --- +## 本次更新 (2026-06-19) — 合规控制报告(SOC2 / ISO 27001) + +将治理证据映射到具名控制项。完整参考:[`docs/source/Zh/doc/new_features/v37_features_doc.rst`](../docs/source/Zh/doc/new_features/v37_features_doc.rst)。 + +- **`build_compliance_report`**(`AC_compliance_report`、`ac_compliance_report`):框架已内建审计员关注的控制项 —— 出口允许清单、JIT 凭证租约、maker-checker 审批、密钥扫描器、审计记录、CycloneDX SBOM。本功能将扁平的 `evidence` 映射表映射到 SOC2(CC6.1/CC6.3/CC6.8/CC7.3/CC8.1)与 ISO 27001(A.5.23/A.8.16/A.8.30)控制项,每项标记为 `satisfied`/`gap`/`not_assessed`,并输出 JSON 或独立 HTML 表格。治理套件的收尾 —— 为报告辅助,非认证。 + ## 本次更新 (2026-06-19) — Agent 轨迹评估 依评分标准为 agent 运行评分。完整参考:[`docs/source/Zh/doc/new_features/v36_features_doc.rst`](../docs/source/Zh/doc/new_features/v36_features_doc.rst)。 diff --git a/README/README_zh-TW.md b/README/README_zh-TW.md index 8c3abbe0..6c05c39e 100644 --- a/README/README_zh-TW.md +++ b/README/README_zh-TW.md @@ -12,6 +12,7 @@ ## 目錄 +- [本次更新 (2026-06-19) — 合規控制報告(SOC2 / ISO 27001)](#本次更新-2026-06-19--合規控制報告soc2--iso-27001) - [本次更新 (2026-06-19) — Agent 軌跡評估](#本次更新-2026-06-19--agent-軌跡評估) - [本次更新 (2026-06-19) — 核准式測試(Golden-Master 基準)](#本次更新-2026-06-19--核准式測試golden-master-基準) - [本次更新 (2026-06-19) — 網路出口允許清單守衛](#本次更新-2026-06-19--網路出口允許清單守衛) @@ -88,6 +89,12 @@ --- +## 本次更新 (2026-06-19) — 合規控制報告(SOC2 / ISO 27001) + +將治理證據對應到具名控制項。完整參考:[`docs/source/Zh/doc/new_features/v37_features_doc.rst`](../docs/source/Zh/doc/new_features/v37_features_doc.rst)。 + +- **`build_compliance_report`**(`AC_compliance_report`、`ac_compliance_report`):框架已內建稽核員關注的控制項 —— 出口允許清單、JIT 憑證租約、maker-checker 審批、密鑰掃描器、稽核記錄、CycloneDX SBOM。本功能將扁平的 `evidence` 對應表映射到 SOC2(CC6.1/CC6.3/CC6.8/CC7.3/CC8.1)與 ISO 27001(A.5.23/A.8.16/A.8.30)控制項,每項標記為 `satisfied`/`gap`/`not_assessed`,並輸出 JSON 或獨立 HTML 表格。治理套件的收尾 —— 為報告輔助,非認證。 + ## 本次更新 (2026-06-19) — Agent 軌跡評估 依評分標準為 agent 執行評分。完整參考:[`docs/source/Zh/doc/new_features/v36_features_doc.rst`](../docs/source/Zh/doc/new_features/v36_features_doc.rst)。 diff --git a/docs/source/Eng/doc/new_features/v37_features_doc.rst b/docs/source/Eng/doc/new_features/v37_features_doc.rst new file mode 100644 index 00000000..35c23b16 --- /dev/null +++ b/docs/source/Eng/doc/new_features/v37_features_doc.rst @@ -0,0 +1,62 @@ +Compliance Control Report (SOC2 / ISO 27001) +============================================ + +AutoControl already ships the *controls* an auditor cares about — a network +egress allowlist, just-in-time credential leases, maker-checker approval, a +secrets scanner, audit logging, a CycloneDX SBOM. :func:`build_compliance_report` +turns "are those controls in place?" into an auditor-readable **control evidence +report**: you supply a flat ``evidence`` mapping of observed facts, and each +catalogued control is marked ``satisfied`` / ``gap`` / ``not_assessed``. + +It is a reporting *aid*, not a certification — it records the evidence you +assert, it does not itself verify the controls. Pure standard library; imports +no ``PySide6``. + +Mapped controls +--------------- + +================ ============= =================================================== ============================== +Framework Control Title Evidence key +================ ============= =================================================== ============================== +SOC2 CC6.1 Logical access restricted to authorized hosts ``egress_allowlist_enforced`` +SOC2 CC6.3 Least-privilege, time-boxed credentials ``jit_credentials_used`` +SOC2 CC6.8 Secrets not hardcoded and scanned ``secrets_scanned`` +SOC2 CC7.3 Security events logged for review ``audit_logging_enabled`` +SOC2 CC8.1 Changes require segregated (maker-checker) approval ``change_approval_required`` +ISO 27001 A.5.23 Information security for cloud/network egress ``egress_allowlist_enforced`` +ISO 27001 A.8.16 Monitoring activities / audit trail ``audit_logging_enabled`` +ISO 27001 A.8.30 Software bill of materials maintained ``sbom_generated`` +================ ============= =================================================== ============================== + +Headless API +------------ + +.. code-block:: python + + from je_auto_control import build_compliance_report, write_compliance_report + + report = build_compliance_report({ + "egress_allowlist_enforced": True, + "jit_credentials_used": True, + "secrets_scanned": True, + "audit_logging_enabled": True, + "change_approval_required": True, + "sbom_generated": True, + }, frameworks=["SOC2"]) # frameworks is optional + + print(report["summary"]) # {satisfied, gap, not_assessed, total} + write_compliance_report(report, "build/compliance.html", fmt="html") + +A control is ``satisfied`` when its evidence key is truthy, ``gap`` when +explicitly falsy, and ``not_assessed`` when the key is absent — so a partial +evidence dict produces an honest gap analysis. ``render_compliance_html`` returns +a standalone HTML table; ``write_compliance_report`` writes ``json`` or ``html``. + +Executor command +---------------- + +``AC_compliance_report`` takes ``evidence`` (a JSON object, or JSON string from +the visual builder), an optional ``frameworks`` list/comma-string, and optional +``path`` + ``fmt`` to write a file; it returns ``{summary, controls, path?}``. +The same operation is exposed as the MCP tool ``ac_compliance_report`` and as a +Script Builder command under **Report**. diff --git a/docs/source/Eng/eng_index.rst b/docs/source/Eng/eng_index.rst index ec038412..45ce6bba 100644 --- a/docs/source/Eng/eng_index.rst +++ b/docs/source/Eng/eng_index.rst @@ -59,6 +59,7 @@ Comprehensive guides for all AutoControl features. doc/new_features/v34_features_doc doc/new_features/v35_features_doc doc/new_features/v36_features_doc + doc/new_features/v37_features_doc doc/ocr_backends/ocr_backends_doc doc/observability/observability_doc doc/operations_layer/operations_layer_doc diff --git a/docs/source/Zh/doc/new_features/v37_features_doc.rst b/docs/source/Zh/doc/new_features/v37_features_doc.rst new file mode 100644 index 00000000..9255ca57 --- /dev/null +++ b/docs/source/Zh/doc/new_features/v37_features_doc.rst @@ -0,0 +1,58 @@ +合規控制報告(SOC2 / ISO 27001) +================================ + +AutoControl 已內建稽核員關注的*控制項* —— 網路出口允許清單、即時憑證租約、 +maker-checker 審批、密鑰掃描器、稽核記錄、CycloneDX SBOM。 +:func:`build_compliance_report` 把「這些控制項是否到位?」轉化為稽核員可讀的**控制證 +據報告**:你提供一個扁平的 ``evidence`` 觀察事實對應表,每個編目控制項即被標記為 +``satisfied`` / ``gap`` / ``not_assessed``。 + +它是報告*輔助工具*,非認證 —— 它記錄你所聲明的證據,並不自行驗證控制項。純標準函式 +庫,不匯入 ``PySide6``。 + +對應的控制項 +------------ + +================ ============= =================================================== ============================== +框架 控制項 標題 證據鍵 +================ ============= =================================================== ============================== +SOC2 CC6.1 邏輯存取限制於授權主機 ``egress_allowlist_enforced`` +SOC2 CC6.3 最小權限、限時憑證 ``jit_credentials_used`` +SOC2 CC6.8 密鑰不硬編碼且經掃描 ``secrets_scanned`` +SOC2 CC7.3 安全事件留存供審查 ``audit_logging_enabled`` +SOC2 CC8.1 變更需職責分離(maker-checker)審批 ``change_approval_required`` +ISO 27001 A.5.23 雲端/網路出口的資訊安全 ``egress_allowlist_enforced`` +ISO 27001 A.8.16 監控活動 / 稽核軌跡 ``audit_logging_enabled`` +ISO 27001 A.8.30 維護軟體物料清單 ``sbom_generated`` +================ ============= =================================================== ============================== + +無頭 API +-------- + +.. code-block:: python + + from je_auto_control import build_compliance_report, write_compliance_report + + report = build_compliance_report({ + "egress_allowlist_enforced": True, + "jit_credentials_used": True, + "secrets_scanned": True, + "audit_logging_enabled": True, + "change_approval_required": True, + "sbom_generated": True, + }, frameworks=["SOC2"]) # frameworks 為選用 + + print(report["summary"]) # {satisfied, gap, not_assessed, total} + write_compliance_report(report, "build/compliance.html", fmt="html") + +當控制項的證據鍵為真時為 ``satisfied``,明確為假時為 ``gap``,鍵不存在時為 +``not_assessed`` —— 因此部分證據字典會產生誠實的缺口分析。``render_compliance_html`` +回傳獨立的 HTML 表格;``write_compliance_report`` 寫出 ``json`` 或 ``html``。 + +執行器指令 +---------- + +``AC_compliance_report`` 接受 ``evidence``(JSON 物件,或視覺化建構器傳入的 JSON 字 +串)、選用的 ``frameworks`` 清單/逗號字串,以及選用的 ``path`` + ``fmt`` 以寫出檔案; +回傳 ``{summary, controls, path?}``。相同操作亦提供為 MCP 工具 +``ac_compliance_report``,以及 Script Builder 中 **Report** 分類下的指令。 diff --git a/docs/source/Zh/zh_index.rst b/docs/source/Zh/zh_index.rst index 88f30c6c..3f269e78 100644 --- a/docs/source/Zh/zh_index.rst +++ b/docs/source/Zh/zh_index.rst @@ -59,6 +59,7 @@ AutoControl 所有功能的完整使用指南。 doc/new_features/v34_features_doc doc/new_features/v35_features_doc doc/new_features/v36_features_doc + doc/new_features/v37_features_doc doc/ocr_backends/ocr_backends_doc doc/observability/observability_doc doc/operations_layer/operations_layer_doc diff --git a/je_auto_control/__init__.py b/je_auto_control/__init__.py index a96d6a1c..5725b8d5 100644 --- a/je_auto_control/__init__.py +++ b/je_auto_control/__init__.py @@ -218,6 +218,10 @@ ) # Agent trajectory evaluation: score a recorded run against a rubric from je_auto_control.utils.trajectory_eval import evaluate_trajectory +# Compliance: map governance evidence to SOC2 / ISO 27001 controls +from je_auto_control.utils.compliance import ( + build_compliance_report, render_compliance_html, write_compliance_report, +) # Background popup/interrupt watchdog (unattended automation) from je_auto_control.utils.watchdog import ( PopupWatchdog, WatchdogRule, default_popup_watchdog, @@ -659,6 +663,8 @@ def start_autocontrol_gui(*args, **kwargs): "ApprovalResult", "approve_artifact", "pending_artifacts", "verify_artifact", "evaluate_trajectory", + "build_compliance_report", "render_compliance_html", + "write_compliance_report", # MCP server "AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt", "MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool", diff --git a/je_auto_control/gui/script_builder/command_schema.py b/je_auto_control/gui/script_builder/command_schema.py index 6cfb0b08..cae12216 100644 --- a/je_auto_control/gui/script_builder/command_schema.py +++ b/je_auto_control/gui/script_builder/command_schema.py @@ -811,6 +811,19 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None: ), description="Score an agent trajectory against a rubric (JSON).", )) + specs.append(CommandSpec( + "AC_compliance_report", "Report", "Compliance Control Report", + fields=( + FieldSpec("evidence", FieldType.STRING, + placeholder='{"egress_allowlist_enforced": true}'), + FieldSpec("frameworks", FieldType.STRING, optional=True, + placeholder="SOC2, ISO27001"), + FieldSpec("path", FieldType.STRING, optional=True), + FieldSpec("fmt", FieldType.ENUM, optional=True, default="json", + choices=("json", "html")), + ), + description="Map governance evidence to SOC2/ISO 27001 controls.", + )) specs.append(CommandSpec( "AC_generate_sop", "Report", "Generate SOP Document", fields=( diff --git a/je_auto_control/utils/compliance/__init__.py b/je_auto_control/utils/compliance/__init__.py new file mode 100644 index 00000000..1466d97f --- /dev/null +++ b/je_auto_control/utils/compliance/__init__.py @@ -0,0 +1,10 @@ +"""Compliance: map automation governance evidence to SOC2 / ISO 27001 controls.""" +from je_auto_control.utils.compliance.compliance_report import ( + CONTROL_CATALOGUE, build_compliance_report, render_compliance_html, + write_compliance_report, +) + +__all__ = [ + "CONTROL_CATALOGUE", "build_compliance_report", "render_compliance_html", + "write_compliance_report", +] diff --git a/je_auto_control/utils/compliance/compliance_report.py b/je_auto_control/utils/compliance/compliance_report.py new file mode 100644 index 00000000..a33baedb --- /dev/null +++ b/je_auto_control/utils/compliance/compliance_report.py @@ -0,0 +1,126 @@ +"""Map AutoControl governance evidence to SOC2 / ISO 27001 controls. + +The framework already ships the *controls* an auditor cares about — an egress +allowlist, just-in-time credential leases, maker-checker approval, a secrets +scanner, audit logging, a CycloneDX SBOM. This module turns "are those controls +in place?" into an auditor-readable **control evidence report**: the caller +supplies a flat ``evidence`` mapping of observed facts, and each catalogued +control is marked ``satisfied`` / ``gap`` / ``not_assessed`` accordingly. + +It is a reporting aid, not a certification: it does not itself verify the +controls, it records the evidence you assert. Pure standard library; imports no +``PySide6``. +""" +import html +import json +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Dict, List, Mapping, Optional, Sequence + +STATUS_SATISFIED = "satisfied" +STATUS_GAP = "gap" +STATUS_NOT_ASSESSED = "not_assessed" + + +@dataclass(frozen=True) +class Control: + """A single mapped control and the evidence key that satisfies it.""" + + control_id: str + framework: str + title: str + evidence_key: str + + +CONTROL_CATALOGUE: Sequence[Control] = ( + Control("CC6.1", "SOC2", "Logical access restricted to authorized hosts", + "egress_allowlist_enforced"), + Control("CC6.3", "SOC2", "Least-privilege, time-boxed credentials", + "jit_credentials_used"), + Control("CC6.8", "SOC2", "Secrets are not hardcoded and are scanned", + "secrets_scanned"), + Control("CC7.3", "SOC2", "Security-relevant events are logged for review", + "audit_logging_enabled"), + Control("CC8.1", "SOC2", "Changes require segregated (maker-checker) approval", + "change_approval_required"), + Control("A.5.23", "ISO27001", "Information security for cloud/network egress", + "egress_allowlist_enforced"), + Control("A.8.16", "ISO27001", "Monitoring activities / audit trail", + "audit_logging_enabled"), + Control("A.8.30", "ISO27001", "Software bill of materials maintained", + "sbom_generated"), +) + + +def _status_for(evidence: Mapping[str, Any], key: str) -> str: + if key not in evidence: + return STATUS_NOT_ASSESSED + return STATUS_SATISFIED if evidence[key] else STATUS_GAP + + +def build_compliance_report(evidence: Mapping[str, Any], + frameworks: Optional[Sequence[str]] = None + ) -> Dict[str, Any]: + """Map ``evidence`` to the control catalogue, optionally filtered. + + ``frameworks`` restricts the report (e.g. ``["SOC2"]``); ``None`` includes + all. Each control is ``satisfied`` (truthy evidence), ``gap`` (explicitly + falsy), or ``not_assessed`` (key absent). + """ + wanted = {f.upper() for f in frameworks} if frameworks else None + controls: List[Dict[str, Any]] = [] + summary = {STATUS_SATISFIED: 0, STATUS_GAP: 0, STATUS_NOT_ASSESSED: 0} + for control in CONTROL_CATALOGUE: + if wanted is not None and control.framework.upper() not in wanted: + continue + status = _status_for(evidence, control.evidence_key) + summary[status] += 1 + controls.append({ + "control_id": control.control_id, "framework": control.framework, + "title": control.title, "evidence_key": control.evidence_key, + "status": status, + }) + summary["total"] = len(controls) + return { + "generated_utc": datetime.now(timezone.utc).isoformat(), + "summary": summary, "controls": controls, + } + + +def render_compliance_html(report: Mapping[str, Any]) -> str: + """Render a compliance ``report`` as a standalone HTML table.""" + summary = report.get("summary", {}) + rows = "".join( + f"" + f"{html.escape(str(c['framework']))}" + f"{html.escape(str(c['control_id']))}" + f"{html.escape(str(c['title']))}" + f"{html.escape(str(c['status']))}" + for c in report.get("controls", [])) + return ( + "" + "Compliance Control Evidence" + "

Compliance Control Evidence

" + f"

Generated {html.escape(str(report.get('generated_utc', '')))} — " + f"satisfied {summary.get(STATUS_SATISFIED, 0)}, " + f"gap {summary.get(STATUS_GAP, 0)}, " + f"not assessed {summary.get(STATUS_NOT_ASSESSED, 0)}.

" + "" + "" + f"{rows}
FrameworkControlTitleStatus
") + + +def write_compliance_report(report: Mapping[str, Any], path: str, + fmt: str = "json") -> str: + """Write ``report`` to ``path`` as ``json`` or ``html``; return the path.""" + output = Path(path) + output.parent.mkdir(parents=True, exist_ok=True) + if fmt == "html": + output.write_text(render_compliance_html(report), encoding="utf-8") + elif fmt == "json": + output.write_text(json.dumps(report, ensure_ascii=False, indent=2), + encoding="utf-8") + else: + raise ValueError(f"unknown compliance report format: {fmt!r}") + return str(output) diff --git a/je_auto_control/utils/executor/action_executor.py b/je_auto_control/utils/executor/action_executor.py index 3c6b7348..77bf3032 100644 --- a/je_auto_control/utils/executor/action_executor.py +++ b/je_auto_control/utils/executor/action_executor.py @@ -3006,6 +3006,23 @@ def _evaluate_trajectory(trajectory: Any, rubric: Any) -> Dict[str, Any]: return evaluate_trajectory(trajectory, rubric) +def _compliance_report(evidence: Any, frameworks: Any = None, + path: Optional[str] = None, + fmt: str = "json") -> Dict[str, Any]: + """Adapter: map governance evidence to SOC2/ISO controls; optionally write.""" + import json + from je_auto_control.utils.compliance import ( + build_compliance_report, write_compliance_report) + if isinstance(evidence, str): + evidence = json.loads(evidence) + if isinstance(frameworks, str): + frameworks = [f.strip() for f in frameworks.split(",") if f.strip()] + report = build_compliance_report(evidence, frameworks) + if path: + report["path"] = write_compliance_report(report, path, fmt) + return report + + class Executor: """ Executor @@ -3252,6 +3269,7 @@ def __init__(self): "AC_approve_artifact": _approve_artifact, "AC_pending_artifacts": _pending_artifacts, "AC_evaluate_trajectory": _evaluate_trajectory, + "AC_compliance_report": _compliance_report, "AC_a11y_record_start": _a11y_record_start, "AC_a11y_record_stop": _a11y_record_stop, "AC_a11y_record_events": _a11y_record_events, diff --git a/je_auto_control/utils/mcp_server/tools/_factories.py b/je_auto_control/utils/mcp_server/tools/_factories.py index b0c4393e..8d201566 100644 --- a/je_auto_control/utils/mcp_server/tools/_factories.py +++ b/je_auto_control/utils/mcp_server/tools/_factories.py @@ -2750,6 +2750,31 @@ def trajectory_eval_tools() -> List[MCPTool]: ] +def compliance_tools() -> List[MCPTool]: + return [ + MCPTool( + name="ac_compliance_report", + description=("Map a flat 'evidence' object (e.g. " + "{egress_allowlist_enforced: true, " + "jit_credentials_used: true, secrets_scanned: true, " + "audit_logging_enabled: true, " + "change_approval_required: true, sbom_generated: " + "true}) to SOC2/ISO 27001 controls. Each is satisfied/" + "gap/not_assessed. Optional 'frameworks' filter, and " + "'path'+'fmt' (json|html) to write. Returns the " + "report {summary, controls}."), + input_schema=schema( + {"evidence": {"type": "object"}, + "frameworks": {"type": "array", "items": {"type": "string"}}, + "path": {"type": "string"}, + "fmt": {"type": "string", "enum": ["json", "html"]}}, + ["evidence"]), + handler=h.compliance_report, + annotations=READ_ONLY, + ), + ] + + def unattended_tools() -> List[MCPTool]: return [ MCPTool( @@ -3809,7 +3834,7 @@ def media_assert_tools() -> List[MCPTool]: ci_annotation_tools, clipboard_history_tools, audit_analysis_tools, process_doc_tools, tween_drag_tools, plugin_sdk_tools, governance_tools, credential_lease_tools, egress_tools, approval_testing_tools, - trajectory_eval_tools, + trajectory_eval_tools, compliance_tools, screen_record_tools, process_and_shell_tools, remote_desktop_tools, gamepad_tools, usb_passthrough_tools, assertion_tools, data_source_tools, diff --git a/je_auto_control/utils/mcp_server/tools/_handlers.py b/je_auto_control/utils/mcp_server/tools/_handlers.py index 4710a2d7..b95cfafd 100644 --- a/je_auto_control/utils/mcp_server/tools/_handlers.py +++ b/je_auto_control/utils/mcp_server/tools/_handlers.py @@ -1331,6 +1331,15 @@ def evaluate_trajectory(trajectory, rubric): return _evaluate(trajectory, rubric) +def compliance_report(evidence, frameworks=None, path=None, fmt="json"): + from je_auto_control.utils.compliance import ( + build_compliance_report, write_compliance_report) + report = build_compliance_report(evidence, frameworks) + if path: + report["path"] = write_compliance_report(report, path, fmt) + return report + + def vlm_locate(description: str, screen_region: Optional[List[int]] = None, model: Optional[str] = None) -> Optional[List[int]]: diff --git a/test/unit_test/headless/test_compliance_report_batch.py b/test/unit_test/headless/test_compliance_report_batch.py new file mode 100644 index 00000000..c533e292 --- /dev/null +++ b/test/unit_test/headless/test_compliance_report_batch.py @@ -0,0 +1,95 @@ +"""Headless tests for the SOC2/ISO compliance control report. Pure stdlib, no +Qt imports.""" +import json + +import pytest + +import je_auto_control as ac +from je_auto_control.utils.compliance import ( + build_compliance_report, render_compliance_html, write_compliance_report) + +FULL_EVIDENCE = { + "egress_allowlist_enforced": True, + "jit_credentials_used": True, + "secrets_scanned": True, + "audit_logging_enabled": True, + "change_approval_required": True, + "sbom_generated": True, +} + + +def test_all_satisfied(): + report = build_compliance_report(FULL_EVIDENCE) + assert report["summary"]["gap"] == 0 + assert report["summary"]["not_assessed"] == 0 + assert report["summary"]["satisfied"] == report["summary"]["total"] + assert all(c["status"] == "satisfied" for c in report["controls"]) + + +def test_gap_and_not_assessed(): + report = build_compliance_report({"egress_allowlist_enforced": False}) + statuses = {c["evidence_key"]: c["status"] for c in report["controls"]} + assert statuses["egress_allowlist_enforced"] == "gap" + assert statuses["sbom_generated"] == "not_assessed" + assert report["summary"]["gap"] >= 1 + assert report["summary"]["not_assessed"] >= 1 + + +def test_framework_filter(): + report = build_compliance_report(FULL_EVIDENCE, frameworks=["SOC2"]) + assert {c["framework"] for c in report["controls"]} == {"SOC2"} + assert report["summary"]["total"] >= 1 + + +def test_html_render_contains_controls(): + report = build_compliance_report(FULL_EVIDENCE) + out = render_compliance_html(report) + assert "= 1 + assert report["path"] == out + + +def test_wiring(): + assert "AC_compliance_report" in ac.executor.known_commands() + from je_auto_control.utils.mcp_server.tools import ( + build_default_tool_registry) + names = {t.name for t in build_default_tool_registry()} + assert "ac_compliance_report" in names + from je_auto_control.gui.script_builder.command_schema import _build_specs + cmds = {s.command for s in _build_specs()} + assert "AC_compliance_report" in cmds + + +def test_facade_exports(): + for attr in ("build_compliance_report", "render_compliance_html", + "write_compliance_report"): + assert hasattr(ac, attr) + assert attr in ac.__all__