diff --git a/tools/sbom-diff-and-risk/README.md b/tools/sbom-diff-and-risk/README.md index 0269d00..879f11e 100644 --- a/tools/sbom-diff-and-risk/README.md +++ b/tools/sbom-diff-and-risk/README.md @@ -23,6 +23,8 @@ machine-readable JSON output shape, see [docs/report-schema.md](docs/report-schema.md). For policy decision explainability fields, see [docs/policy-decision-explainability.md](docs/policy-decision-explainability.md). +For CI consumption of policy decision fields, see +[docs/policy-decision-ci-cookbook.md](docs/policy-decision-ci-cookbook.md). For CI consumption of summary-only output, see [docs/summary-json-ci-cookbook.md](docs/summary-json-ci-cookbook.md). For a consumer-facing GitHub Actions example, see diff --git a/tools/sbom-diff-and-risk/docs/policy-decision-ci-cookbook.md b/tools/sbom-diff-and-risk/docs/policy-decision-ci-cookbook.md new file mode 100644 index 0000000..3cfc179 --- /dev/null +++ b/tools/sbom-diff-and-risk/docs/policy-decision-ci-cookbook.md @@ -0,0 +1,133 @@ +# Policy decision CI cookbook + +This page shows how to consume policy decision explanation fields from +`report.json` in CI without changing the `sbom-diff-risk` analysis model. + +Use this when a repository wants a small job summary that explains local policy +blocks, warnings, or suppressions in machine-readable terms. + +## Minimal command + +```bash +sbom-diff-risk compare \ + --before examples/cdx_before.json \ + --after examples/cdx_after.json \ + --policy examples/policy-strict.yml \ + --out-json outputs/policy-report.json +``` + +The strict example policy can make the command return a policy failure exit +code. In CI, keep the generated `outputs/policy-report.json` artifact so the +policy decision metadata remains available for review. + +## Python consumer + +This example reads the full JSON report, prints compact policy status, and then +prints the stable explanation fields for blocking and warning findings. + +```python +import json +from pathlib import Path + +report = json.loads( + Path("outputs/policy-report.json").read_text(encoding="utf-8") +) + +policy = report.get("summary", {}).get("policy") +if policy is None: + print("policy=not-used") + raise SystemExit(0) + +print( + "policy=" + f"{policy['status']} " + f"blocking={policy['blocking']} " + f"warning={policy['warning']} " + f"suppressed={policy['suppressed']}" +) + +findings = ( + report.get("blocking_findings", []) + + report.get("warning_findings", []) + + report.get("suppressed_findings", []) +) + +for finding in findings: + print( + "policy-finding " + f"level={finding.get('level')} " + f"rule={finding.get('policy_rule')} " + f"reason={finding.get('decision_reason')} " + f"severity_source={finding.get('severity_source')} " + f"observed={finding.get('observed_value')} " + f"threshold={finding.get('matched_threshold')}" + ) + +if policy["status"] == "fail": + raise SystemExit("local policy failed") +``` + +The final failure is based on the local policy status already produced by the +tool. The snippet does not create a new package safety verdict. + +## PowerShell consumer + +This example uses `ConvertFrom-Json` to print the same policy status and +explanation fields. + +```powershell +$report = Get-Content outputs/policy-report.json -Raw | ConvertFrom-Json +$policy = $report.summary.policy + +if ($null -eq $policy) { + Write-Output "policy=not-used" + exit 0 +} + +Write-Output ( + "policy={0} blocking={1} warning={2} suppressed={3}" -f + $policy.status, + $policy.blocking, + $policy.warning, + $policy.suppressed +) + +$findings = @() +$findings += @($report.blocking_findings) +$findings += @($report.warning_findings) +$findings += @($report.suppressed_findings) + +foreach ($finding in $findings) { + Write-Output ( + "policy-finding level={0} rule={1} reason={2} severity_source={3} observed={4} threshold={5}" -f + $finding.level, + $finding.policy_rule, + $finding.decision_reason, + $finding.severity_source, + $finding.observed_value, + $finding.matched_threshold + ) +} + +if ($policy.status -eq "fail") { + throw "local policy failed" +} +``` + +## Compatibility notes + +- `summary.policy` appears only when policy evaluation is applied. +- Policy decision explanation fields appear only on policy finding objects. +- `risks` remains the local heuristic finding list; use policy finding sections + when you need policy-decision metadata. +- Consumers should treat unrecognized future fields as additive report data. +- Use `summary.policy` for compact status and counts. +- Use policy finding explanation fields for reviewer-facing detail. + +## Non-claims + +- The policy decision fields are not CVE results. +- The policy decision fields are not dependency safety verdicts. +- The snippets do not add network behavior. +- The snippets do not replace human review of local policy choices. +- Production PyPI publishing remains intentionally deferred. diff --git a/tools/sbom-diff-and-risk/docs/policy-decision-explainability.md b/tools/sbom-diff-and-risk/docs/policy-decision-explainability.md index c95ccaa..4ebc98c 100644 --- a/tools/sbom-diff-and-risk/docs/policy-decision-explainability.md +++ b/tools/sbom-diff-and-risk/docs/policy-decision-explainability.md @@ -77,9 +77,8 @@ the matching rule was active through `warn_on`. ## CI and review usage Consumers can use these fields to group policy findings by rule, explain why a -local gate failed, or build small job summaries. For example, a CI step can read -`blocking_findings`, print each `policy_rule` and `decision_reason`, and fail -only because the tool already returned a policy failure exit code. +local gate failed, or build small job summaries. For CI examples, see +[policy-decision-ci-cookbook.md](policy-decision-ci-cookbook.md). Use `summary.policy` for compact counts and status. Use policy finding explanation fields when a reviewer needs to inspect why the status was diff --git a/tools/sbom-diff-and-risk/docs/report-schema.md b/tools/sbom-diff-and-risk/docs/report-schema.md index 4061013..b1ba434 100644 --- a/tools/sbom-diff-and-risk/docs/report-schema.md +++ b/tools/sbom-diff-and-risk/docs/report-schema.md @@ -72,7 +72,9 @@ policy-decision metadata unless a policy evaluation maps them into policy findings. For reviewer-facing examples and interpretation guidance, see -[policy-decision-explainability.md](policy-decision-explainability.md). +[policy-decision-explainability.md](policy-decision-explainability.md). For CI +consumer snippets, see +[policy-decision-ci-cookbook.md](policy-decision-ci-cookbook.md). ## Summary contract diff --git a/tools/sbom-diff-and-risk/docs/reviewer-evidence-pack.md b/tools/sbom-diff-and-risk/docs/reviewer-evidence-pack.md index b2bed79..1d6db35 100644 --- a/tools/sbom-diff-and-risk/docs/reviewer-evidence-pack.md +++ b/tools/sbom-diff-and-risk/docs/reviewer-evidence-pack.md @@ -83,6 +83,9 @@ For policy finding interpretation, see documents the policy decision metadata used to explain local blocks, warnings, and suppressions. +For CI job-summary examples that consume policy decision metadata, see +[policy-decision-ci-cookbook.md](policy-decision-ci-cookbook.md). + For CI dashboard, job-summary, and local-threshold examples that consume `outputs/summary.json`, see [summary-json-ci-cookbook.md](summary-json-ci-cookbook.md). diff --git a/tools/sbom-diff-and-risk/docs/summary-json-ci-cookbook.md b/tools/sbom-diff-and-risk/docs/summary-json-ci-cookbook.md index 8b4c4fd..06ba400 100644 --- a/tools/sbom-diff-and-risk/docs/summary-json-ci-cookbook.md +++ b/tools/sbom-diff-and-risk/docs/summary-json-ci-cookbook.md @@ -21,6 +21,10 @@ sbom-diff-risk compare \ The full report remains available at `outputs/report.json`. The compact summary-only object is written to `outputs/summary.json`. +For CI examples that consume detailed policy decision explanation fields from +the full JSON report, see +[policy-decision-ci-cookbook.md](policy-decision-ci-cookbook.md). + ## Python consumer This example reads the summary and applies an explicit local threshold. The