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

Domain owners + CODEOWNERS

Ownership is not optional in Kirimana. Every domain declares at least one owner, and that single fact drives two mechanisms: GitHub auto-requests review from the right people when a contract PR lands, and the PR-time lint refuses to merge a domain whose owner is a placeholder. You maintain ownership in one place — kiri.yml — and both mechanisms read from it.

Ownership lives in kiri.yml

Owners are declared per domain, alongside the domain’s other policy:

domains:
  sales:
    owners: [sales-team, alice@acme.com]
  hr:
    owners: [hr-team]
  finance:
    owners: [finance-team, dana@acme.com]

Owners can be GitHub team handles (sales-team), bare emails (alice@acme.com), or handles already spelled with a leading @. A domain must carry at least one owner — this is the anchor for everything downstream. Keeping ownership in kiri.yml rather than in a hand-edited CODEOWNERS file means the platform team reviews ownership changes through the same PR flow as every other governance edit, and there is exactly one source of truth to keep honest.

Auto-generating CODEOWNERS

GitHub’s CODEOWNERS file drives review-request routing: when a PR touches a file under a matched path, GitHub automatically requests review from the owners that path declares. Kirimana renders this file from your domain owners so you never hand-maintain it:

# Print to stdout (inspect before writing):
kiri contract codeowners

# Write it into place:
kiri contract codeowners --output .github/CODEOWNERS

The output is one line per domain, sorted deterministically, mapping the domain’s contract path to its owners:

# Auto-generated by `kiri contract codeowners`.
# Do not edit by hand — edit kiri.yml domain owners and regenerate.
contracts/finance/**  @finance-team dana@acme.com
contracts/hr/**       @hr-team
contracts/sales/**    @sales-team alice@acme.com

Team handles get a leading @ automatically; bare emails pass through verbatim (GitHub accepts them when the user has a verified email); entries already prefixed with @ are left alone. Because the render is byte-deterministic, the file is safe to commit and diff — a change to it is always a real change to ownership, never formatting noise.

CODEOWNERS only controls who gets pinged. It does not set how many approvals are required — that is the per-domain approval count, resolved separately at PR time (see PR-time governance gates). The two compose: CODEOWNERS routes the review to the right people, the approval count decides how many of them must sign off.

Keeping the file honest in CI

A hand-edited CODEOWNERS file would silently bypass kiri.yml and break the single-source-of-truth guarantee. To prevent that, run the command in --check mode in CI:

kiri contract codeowners --output .github/CODEOWNERS --check

--check re-renders from kiri.yml and compares against the committed file. It exits non-zero — failing the workflow — if the file is missing, or if it drifts from what the current domain owners would produce. The fix is always the same: regenerate and commit. This is the same generated-artifact-drift pattern Kirimana uses to fence other emitted files, applied to review routing.

Owner validation at lint

Routing a PR to an owner only helps if the owner is real. The PR-time contract lint carries an owner-placeholder rule that fails on owner fields that are obviously not a real accountable party:

  • Known placeholders — owner@example.com, TODO, FIXME, example.com-style stand-ins.
  • Malformed emails — a value containing @ that is not a well-formed address.

Team-handle-style owners (ops, analytics-team) pass, since not every owner is an email. The point of the rule is narrow and deliberate: a contract cannot ship with ownership that resolves to nobody. Discovery and scaffolding leave placeholder owners behind by design — the lint is what forces a human to replace them before the contract is governed for real.

Run the lint locally the same way CI does:

kiri contract lint

An owner-placeholder finding blocks the merge until you set a genuine owner in kiri.yml and regenerate CODEOWNERS.

The full ownership loop

Putting it together, ownership flows in one direction and self-checks at every hop:

  1. A platform-admin sets or changes a domain’s owner in kiri.yml.
  2. kiri contract codeowners --output .github/CODEOWNERS renders the routing file; it is committed in the same PR.
  3. CI runs kiri contract codeowners --check — the committed file must match the render, or the workflow fails.
  4. CI runs kiri contract lint — placeholder or malformed owners fail the workflow.
  5. On the next contract PR for that domain, GitHub auto-requests review from exactly the owners kiri.yml declares.

You never touch .github/CODEOWNERS by hand, you never route a PR to the wrong team, and you never merge a contract that nobody owns. Each of those guarantees comes from the same declared fact: the domain’s owner in kiri.yml.

For how many approvals those owners must give — and how the count rises for sensitive changes — see PR-time governance gates. For who is allowed to edit the kiri.yml owner list in the first place, see RBAC roles + capabilities.

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