Files
cozystack/hack/codeowners-invariant.bats
Andrei Kvapil 361c729417 test(codeowners): guard the coverage invariant with a unit check
CODEOWNERS is last-match-wins, so a new scoped rule that omits the
default owners silently strips their ownership for that path. Add a
hack/*.bats unit test (auto-discovered by make bats-unit-tests) that
requires every rule listing owners to repeat the owners of the *
catch-all, exempting ownerless rules and the governance roster.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
2026-08-10 10:30:30 +02:00

49 lines
2.3 KiB
Bash

#!/usr/bin/env bats
# -----------------------------------------------------------------------------
# Coverage invariant for .github/CODEOWNERS.
#
# CODEOWNERS applies only the LAST matching rule, and matching is not
# additive: a scoped rule fully replaces the * catch-all instead of extending
# it. The only way to keep the default owners able to review everything is to
# repeat their handles in every scoped rule. A rule that omits them silently
# carves its path out from under the catch-all — that is how /hack/,
# /.github/, /Makefile, /.pre-commit-config.yaml and /docs/ ended up owned by
# exactly two people, deadlocking every PR one of them authored while the
# other was away ("Require review from Code Owners" is enforced on main, and
# stale reviews are dismissed on push, so there is no way around a missing
# owner short of an admin override).
#
# The invariant: every rule that lists owners must include every owner of the
# * catch-all. Two deliberate exceptions:
# - ownerless rules — generated files whose review is waived on purpose;
# - the governance roster, owned by exactly @tym83 @kvaps. It is identified
# by that owner set rather than by its position in the file, so the
# exemption cannot leak to rules appended under other sections.
#
# Changing the catch-all owners or growing the governance roster is a policy
# change; this test failing on such a change is the point — adjust the
# expectation here in the same PR, with the policy discussion linked.
# -----------------------------------------------------------------------------
@test "every CODEOWNERS rule that lists owners repeats the catch-all owners (governance excepted)" {
[ -f .github/CODEOWNERS ]
awk '
/^[[:space:]]*(#|$)/ { next }
$1 == "*" { for (i = 2; i <= NF; i++) defaults[$i]; ndef = NF - 1; next }
NF < 2 { next }
NF == 3 && (($2 == "@tym83" && $3 == "@kvaps") || ($2 == "@kvaps" && $3 == "@tym83")) { next }
{
for (d in defaults) {
found = 0
for (i = 2; i <= NF; i++) if ($i == d) found = 1
if (!found) { printf "line %d: rule %s is missing default owner %s\n", FNR, $1, d; bad = 1 }
}
}
END {
if (ndef == 0) { print "no * catch-all rule with owners found"; exit 1 }
exit bad
}
' .github/CODEOWNERS
}