From 5b4f6eb222403ed6378b90362d085738ddd7e25c Mon Sep 17 00:00:00 2001 From: stacknil Date: Mon, 11 May 2026 08:47:16 +0800 Subject: [PATCH] [codex] Add policy JSON Actions consumer example --- tools/sbom-diff-and-risk/README.md | 1 + .../docs/github-actions-consumer-example.md | 6 + .../docs/policy-decision-ci-cookbook.md | 5 + .../github-actions-policy-consumer.yml | 117 ++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 tools/sbom-diff-and-risk/examples/github-actions-policy-consumer.yml diff --git a/tools/sbom-diff-and-risk/README.md b/tools/sbom-diff-and-risk/README.md index e64d3bf..a1d3943 100644 --- a/tools/sbom-diff-and-risk/README.md +++ b/tools/sbom-diff-and-risk/README.md @@ -308,6 +308,7 @@ The [examples/](examples/) directory includes: - a sample pass JSON report at [sample-report.json](examples/sample-report.json) - a sample summary-only JSON artifact at [sample-summary.json](examples/sample-summary.json) - a consumer GitHub Actions workflow example at [github-actions-consumer.yml](examples/github-actions-consumer.yml) +- a policy-gated consumer GitHub Actions workflow example at [github-actions-policy-consumer.yml](examples/github-actions-policy-consumer.yml) - a sample pass Markdown report at [sample-report.md](examples/sample-report.md) - sample policy-warn reports at [sample-policy-warn-report.json](examples/sample-policy-warn-report.json) and [sample-policy-warn-report.md](examples/sample-policy-warn-report.md) - sample policy-fail reports at [sample-policy-fail-report.json](examples/sample-policy-fail-report.json) and [sample-policy-fail-report.md](examples/sample-policy-fail-report.md) diff --git a/tools/sbom-diff-and-risk/docs/github-actions-consumer-example.md b/tools/sbom-diff-and-risk/docs/github-actions-consumer-example.md index 6ac5604..6773677 100644 --- a/tools/sbom-diff-and-risk/docs/github-actions-consumer-example.md +++ b/tools/sbom-diff-and-risk/docs/github-actions-consumer-example.md @@ -22,6 +22,10 @@ The same workflow is also checked in as [../examples/github-actions-consumer.yml](../examples/github-actions-consumer.yml) for copying into consumer repositories. +For a policy-gated variant that writes `outputs/policy.json` with +`--policy-json PATH`, see +[../examples/github-actions-policy-consumer.yml](../examples/github-actions-policy-consumer.yml). + ```yaml name: Dependency diff review @@ -143,3 +147,5 @@ from that local checkout instead of downloading a release wheel: For compact summary consumption patterns, see [summary-json-ci-cookbook.md](summary-json-ci-cookbook.md). +For policy sidecar consumption patterns, see +[policy-decision-ci-cookbook.md](policy-decision-ci-cookbook.md). 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 index 19649e2..c203c08 100644 --- a/tools/sbom-diff-and-risk/docs/policy-decision-ci-cookbook.md +++ b/tools/sbom-diff-and-risk/docs/policy-decision-ci-cookbook.md @@ -25,6 +25,11 @@ decision metadata remains available for review. For a checked-in reference artifact generated from this path, see [sample-policy.json](../examples/sample-policy.json). +For a full GitHub Actions consumer workflow example that captures +`outputs/policy.json`, uploads it even when local policy fails, and then fails +the job based on `summary.policy`, see +[github-actions-policy-consumer.yml](../examples/github-actions-policy-consumer.yml). + ## Python consumer This example reads the policy-only JSON sidecar, prints compact policy status, diff --git a/tools/sbom-diff-and-risk/examples/github-actions-policy-consumer.yml b/tools/sbom-diff-and-risk/examples/github-actions-policy-consumer.yml new file mode 100644 index 0000000..cd88312 --- /dev/null +++ b/tools/sbom-diff-and-risk/examples/github-actions-policy-consumer.yml @@ -0,0 +1,117 @@ +# Example only. +# Copy this file into a consumer repository under .github/workflows/ if useful. +# This repository does not run this file as a workflow. +# Production PyPI publishing for sbom-diff-and-risk is intentionally deferred; +# install from a GitHub Release asset or local checkout instead. + +name: Dependency policy review + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + dependency-policy: + runs-on: ubuntu-latest + + steps: + - name: Check out consumer repository + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - name: Download sbom-diff-and-risk release wheel + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p .tooling/sbom-diff-risk + gh release download v0.8.0 \ + --repo stacknil/scientific-computing-toolkit \ + --pattern "sbom_diff_and_risk-0.8.0-py3-none-any.whl" \ + --dir .tooling/sbom-diff-risk + + - name: Install sbom-diff-risk + run: | + python -m pip install \ + .tooling/sbom-diff-risk/sbom_diff_and_risk-0.8.0-py3-none-any.whl + + - name: Compare dependency evidence with local policy + id: compare + run: | + mkdir -p outputs + set +e + sbom-diff-risk compare \ + --before path/to/before-sbom.json \ + --after path/to/after-sbom.json \ + --format auto \ + --policy path/to/policy.yml \ + --out-json outputs/report.json \ + --out-md outputs/report.md \ + --policy-json outputs/policy.json \ + --out-sarif outputs/report.sarif + status=$? + set -e + echo "$status" > outputs/policy-exit-code.txt + echo "exit_code=$status" >> "$GITHUB_OUTPUT" + + - name: Summarize local policy decision + run: | + python - <<'PY' + import json + from pathlib import Path + + policy_report = json.loads( + Path("outputs/policy.json").read_text(encoding="utf-8") + ) + policy = 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 = ( + policy_report.get("blocking_findings", []) + + policy_report.get("warning_findings", []) + + policy_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") + PY + + - name: Upload dependency policy outputs + if: always() + uses: actions/upload-artifact@v7 + with: + name: dependency-policy-outputs + path: | + outputs/report.json + outputs/report.md + outputs/policy.json + outputs/policy-exit-code.txt + outputs/report.sarif