audit-labs/control-coverage
Control coverage and blind-spot analysis for audit evidence.
clone: git clone https://gitbay.org/audit-labs/control-coverage.git
main: control_coverage/reporters/json.py · raw
1"""JSON renderer — the coverage result as a machine-readable document.
2
3Stable key order so two runs diff cleanly. Suitable for dashboards, ticketing,
4or gating a pipeline on the coverage percentage.
5"""
6
7from __future__ import annotations
8
9import json as _json
10from typing import TYPE_CHECKING
11
12from .. import __version__
13
14if TYPE_CHECKING:
15 from ..coverage import CoverageReport
16
17
18def to_dict(report: CoverageReport) -> dict:
19 return {
20 "subject": report.subject,
21 "generated_at": report.generated_at,
22 "source_count": report.source_count,
23 "tool": {"name": "control-coverage", "version": __version__},
24 "frameworks": [
25 {
26 "framework": fc.catalog.framework,
27 "name": fc.catalog.name,
28 "version": fc.catalog.version,
29 "sha256": fc.catalog.sha256,
30 "catalog_coverage": fc.catalog.coverage,
31 "in_scope": fc.in_scope,
32 "addressed": fc.addressed,
33 "supported": fc.supported,
34 "coverage_pct": fc.coverage_pct,
35 "assured_pct": fc.assured_pct,
36 "counts": fc.counts,
37 "controls": [
38 {
39 "id": r.control.id,
40 "code": r.control.code,
41 "title": r.control.title,
42 "family": r.control.family,
43 "state": r.state,
44 "owner": r.owner,
45 "exclusion_reason": r.exclusion_reason,
46 "checked_by": sorted({o.rule_id for o in r.observations if o.rule_id}),
47 "sources": sorted({o.source for o in r.observations}),
48 }
49 for r in fc.results
50 ],
51 }
52 for fc in report.frameworks
53 ],
54 "orphan_codes": report.orphan_codes,
55 }
56
57
58def render(report: CoverageReport) -> str:
59 return _json.dumps(to_dict(report), indent=2, sort_keys=False) + "\n"