Skip to main content
Private Preview·Early access by invitation.Request access →
Kirimana.
Docs · Governance

PR-time governance gates in CI

Governance that only lives in a runbook gets skipped. Kirimana pushes it into the pull request: a set of CLI checks run in GitHub Actions on every contract change, and a failing check blocks the merge. The same commands run locally, so a developer sees the same result before pushing that CI will see after. This page covers what each gate checks, what blocks, and a workflow you can drop in.

The commands that gate a PR

Four commands do the work. All of them are safe to run locally and exit non-zero on a finding, which is what fails the CI job.

  • kiri contract lint — PR-time governance checks over the contracts touched in the diff (classification presence, owner validity, AI-policy coherence, column-lineage resolution).
  • kiri contract validate — structural and semantic validation of a contract (well-formed ODCS, closed-menu enum values, schema coherence).
  • kiri contract approval-count — resolves how many approvals this specific PR requires, so the workflow can enforce it.
  • kiri contract codeowners --check — fails if the committed CODEOWNERS file has drifted from kiri.yml domain owners.

For projects on the per-(domain, layer) shape, kiri layer validate is the structural gate over the layer files themselves — it returns zero findings on a well-formed project and hard-fails on the invariant violations described in hub-and-spoke governance.

What blocks a merge

kiri contract lint emits typed findings, and an error-severity finding fails the job. The rules that matter most at the gate:

  • classification-missing — a contract with no kiri.classification property. Every asset must declare its data class; an unclassified contract cannot ship.
  • owner-placeholder — an owner field that is a placeholder (owner@example.com, TODO, FIXME) or a malformed email. The domain must resolve to a real accountable party.
  • ai-policy-coherence — a classification / AI-policy mismatch, the classic “raised classification to confidential but left inference allowed” slip. A confidential or restricted contract that still permits inference is flagged.
  • column-lineage-unresolvable — a silver/gold column declares an upstream column URN that does not resolve to a real upstream. A declared lineage edge that points at nothing is a broken contract.
  • column-lineage-missing — a silver/gold property with no kiri.lineage.from_columns. This is a warning during the migration window rather than a hard block, so a large estate can adopt lineage incrementally.

kiri contract validate blocks on anything structurally wrong: a malformed ODCS document, a value outside a closed-menu enum, an inconsistent schema. And kiri contract codeowners --check blocks on docs drift between the generated CODEOWNERS file and current domain owners — a hand-edit to routing fails the job until it is regenerated.

Resolving the approval count

The required number of approvals is not a fixed repo setting — it rises with the sensitivity of the change. kiri contract approval-count computes it per PR from three inputs the workflow derives with git diff:

  • changed paths — the per-domain base approval count for every domain the PR touches.
  • restricted-raises — paths whose diff introduced value: restricted. Raising a classification to restricted demands elevated approval.
  • cross-domain edits — paths that introduce a new cross-domain consumer. A new dependency between domains is a two-team decision.

The command prints a single integer on stdout — the number the workflow feeds into its required-approvals check. Because the count is computed from the diff, a routine change inside one domain needs the domain’s base count, while a change that raises restricted or adds a cross-domain edge automatically needs more.

Example workflow

A minimal .github/workflows/contract-governance.yml:

name: contract-governance
on:
  pull_request:
    paths:
      - "01-config/**"
      - "contracts/**"
      - "kiri.yml"

jobs:
  govern:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0            # need the base ref for the diff

      - name: Install kiri
        run: pip install kiri-cli

      # Structural + semantic validation.
      - name: Validate contracts
        run: kiri contract validate

      # Governance findings (classification, owner, AI policy, lineage).
      - name: Lint contracts
        run: kiri contract lint

      # CODEOWNERS must match kiri.yml — no hand-edits.
      - name: Check CODEOWNERS drift
        run: kiri contract codeowners --output .github/CODEOWNERS --check

      # Compute the required approver count for this PR.
      - name: Compute changed paths
        run: |
          git diff --name-only origin/${{ github.base_ref }}...HEAD \
            > changed.txt
          git diff origin/${{ github.base_ref }}...HEAD \
            | grep -l 'value: restricted' changed.txt > raises.txt || true

      - name: Required approvals
        id: approvals
        run: |
          echo "count=$(kiri contract approval-count \
            --changed-files-from changed.txt \
            --restricted-raises-from raises.txt)" >> "$GITHUB_OUTPUT"

      - name: Enforce approvals
        run: |
          echo "This PR requires ${{ steps.approvals.outputs.count }} approval(s)."
          # Wire the count into your branch-protection / merge-gate check.

Adapt the last step to your merge-gate mechanism — the point is that approval-count gives you the number, and your branch protection enforces it. CODEOWNERS (from the previous page) has already routed the review to the right people; this makes sure enough of them sign off before the change lands.

Why the gate lives in the PR

Every one of these checks runs identically on a laptop and in CI, so the feedback loop is tight: kiri contract lint before you push tells you exactly what the merge gate will say. Nothing is enforced by convention or by a reviewer remembering to look — an unclassified contract, a placeholder owner, a dangling lineage edge, or a drifted CODEOWNERS file simply cannot merge. Governance becomes a property of the pipeline rather than a discipline you have to sustain by hand.

Once a change clears the gate but still needs a recorded human decision, that moves into the contract-approval review.

Updated 5 July 2026 · v1.0.0-beta.1