Decision Gate Docs

Deterministic, replayable gate evaluation with auditable decisions.

Asset Core docs

CI Release Gate Dogfooding

This guide documents how Decision Gate dogfoods itself by gating release eligibility using deterministic CI evidence. The goal is to demonstrate a real, auditable policy layer without replacing the CI system.

Why This Is Rigorous

  • Separation of concerns: CI executes tests; Decision Gate evaluates the policy and emits a deterministic decision.
  • Evidence-driven: The decision is based on a versioned evidence bundle rather than implicit CI logs.
  • Auditable output: Decision Gate exports a runpack with the full decision trail and verification data.
  • Deterministic: The same evidence bundle yields the same decision.

What Is Gated

The release tag workflow validates that a release is eligible based on:

  • Formatting, lint, and unit tests
  • System tests P0 and P1
  • cargo-deny
  • Generator drift checks
  • Complete SBOM coverage for every release subject
  • Provenance attestation verification for every release subject
  • Keyless OIDC signature verification for artifact and provenance payloads
  • Vulnerability policy pass (High/Critical blocked + KEV blocked at any severity)
  • Packaging dry runs (Python + TypeScript)
  • Docker smoke test
  • Tag/version consistency (tag matches workspace version)

If any requirement is missing or false, the gate denies the release.

Evidence Bundle

The release workflow writes a JSON evidence bundle to an evidence workspace root and evaluates it via a root-relative file path. The default example is ./evidence/release_evidence.json (relative to the repo root), but the root can be overridden at runtime with --json-root.

Example (shape only):

{
  "release": {
    "tag": "v0.1.0",
    "version": "0.1.0",
    "tag_matches_version": true,
    "sha": "<git sha>",
    "generated_at": 1710000000000,
    "sbom_path": ".tmp/ci/sbom/decision-gate.sbom.spdx.json"
  },
  "checks": {
    "fmt": true,
    "clippy": true,
    "cargo_deny": true,
    "generate_all": true,
    "unit_tests": true,
    "system_tests_p0": true,
    "system_tests_p1": true,
    "sbom": true,
    "sbom_complete": true,
    "sbom_verified": true,
    "provenance_verified": true,
    "signature_verified": true,
    "vuln_policy_pass": true,
    "package_dry_run": true,
    "docker_smoke": true
  }
}

Policy Scenario

The release gate is expressed as a standard Decision Gate scenario:

  • Template: configs/ci/release_gate_scenario.json
  • Policy: All conditions must be true (And gate)
  • Provider: built-in json provider reads the evidence bundle
  • Execution: live scenario run (not precheck) so a runpack is produced

The scenario is instantiated at runtime by replacing the template placeholders:

  • {{SCENARIO_ID}} -> unique scenario identifier
  • {{EVIDENCE_FILE}} -> relative path to the evidence bundle (within the json provider root)

How It Runs in CI

The release workflow performs the following sequence:

  1. Runs CI checks (fmt, clippy, tests, deny, packaging, smoke test).
  2. Generates release supply-chain evidence with scripts/ci/supply_chain_generate.sh (subjects, SBOMs, provenance, keyless signatures, vulnerability artifacts).
  3. Verifies supply-chain evidence with scripts/ci/supply_chain_verify.sh as a hard gate.
  4. Writes the Decision Gate release evidence bundle, including the new supply-chain verification booleans.
  5. Starts a local MCP server with configs/presets/ci-release-gate.toml (optionally adding --json-root <evidence-root> and --json-root-id <root-id>).
  6. Evaluates the scenario using the evidence bundle.
  7. Exports and verifies a runpack.
  8. Uploads artifacts:
    • Evidence bundle
    • Runpack
    • Supply-chain evidence bundle (SBOM/provenance/signatures/vuln artifacts)
    • Decision payload and summary
    • Artifact name: decision-gate-release-gate

The implementation lives in scripts/ci/ci_release_gate.sh and is called by the release workflow.

How Publishing Is Guarded

Publishing is a separate manual workflow (.github/workflows/publish.yml). Before publish steps execute, it now runs target-scoped scripts/ci/supply_chain_generate.sh + scripts/ci/supply_chain_verify.sh for the selected publish target. It also verifies the tag release workflow completed successfully, so publish cannot proceed without both:

  • passing, audited Decision Gate release eligibility; and
  • passing supply-chain verification for the target artifacts.

Running Locally

You can run the same release gate locally with a custom evidence bundle:

python3 - <<'PY'
import json
from pathlib import Path

Path("evidence/release_evidence.json").write_text(json.dumps({
    "release": {
        "tag": "v0.1.0",
        "version": "0.1.0",
        "tag_matches_version": True,
        "sha": "local",
        "generated_at": 0,
        "sbom_path": "evidence/sbom/decision-gate.sbom.spdx.json",
    },
    "checks": {
        "fmt": True,
        "clippy": True,
        "cargo_deny": True,
        "generate_all": True,
        "unit_tests": True,
        "system_tests_p0": True,
        "system_tests_p1": True,
        "sbom": True,
        "package_dry_run": True,
        "docker_smoke": True,
    },
}, indent=2))
PY

bash scripts/ci/ci_release_gate.sh \
  --evidence-file evidence/release_evidence.json \
  --output-dir evidence/release-runpack \
  --config configs/presets/ci-release-gate.toml

If any check is false, the script exits non-zero and the decision summary will show the denial reason.

To run local release-parity supply-chain generation + verification before gate evaluation:

bash scripts/ci/verify_all.sh --release-parity

Artifacts to Inspect

  • decision_payload.json: raw Decision Gate response payload
  • decision_summary.json: decision kind + allow/deny
  • runpack/manifest.json: deterministic runpack manifest
  • runpack_verify.json: runpack verification output
  • Docs/architecture/decision_gate_ci_and_workflow_architecture.md
  • configs/ci/release_gate_scenario.json
  • scripts/ci/ci_release_gate.sh