---
title: 'Ownership and Approval Rules'
source: 'https://academia.sh/en/courses/code-review/ownership-and-approval-rules'
course: 'Code Review and Team Process'
language: en
updated: '2026-08-17T18:10:46+00:00'
license: 'CC BY-SA 4.0'
---

# Ownership and Approval Rules

Raising the required reviewer count to one, two, and three leaves found fixed at 6, rounds at 6, and wait at 36; a single open approval alone finds 22 on the same change, and the course's second claim shows up here in reverse — ownership is not a choice of person, it is a choice of axis.

The previous lesson measured the contribution guide: a written expectation lowers the
defects entering review and buys rounds in a narrow review. The guide writes what will be
met. Once the change is submitted, another question is born and the guide does not answer
it: **who will look?**

In some repositories this question is open; in some it is answered by a rule bound to the
file path. A directory has an **owner**, and every change touching that directory waits on
the owner's approval. This lesson's question is not whether the rule is fair — it is **how
many axes the rule opens** and what the number of axes opened does to defects found.

## Code Ownership Is a Path Rule

**Code ownership** is a mapping that binds sections of the repository to roles. The rule
text lists paths and roles; when a change is submitted, the paths it touches are looked
at and the corresponding roles are added as **required reviewers**.

```text
# example ownership rule — not executed

# path prefix         required reviewer role
/                      maintenance
/core/                 core-owner
/core/protocol/        protocol-owner
/docs/                 docs-owner
```

Three properties are immediately visible. The rule looks at the **path**, not the
change's content: a typo fix under `/core/` also calls the core owner. The rule applies
the **most specific match**: a change under `/core/protocol/` calls the protocol owner.
And the rule is a **gate**: the change does not merge without the owner's approval.

The rule looking at the path works in two directions. On one hand it is automatic and not
forgotten: no one thinks "who should look at this," the mapping runs on its own. On the
other, the rule **knows the path, not the change.** Of two changes touching the same file,
one might carry an interface decision, the other might add a log line; the rule calls the
same owner for both. The measurement tests exactly this second direction: because the
owner's view is independent of the change, the axis set the rule opens is also independent
of the change.

The need the rule meets is clear. Not everyone knows every section of the repository; the
role that best knows a section's history, its reasoning, and its pitfalls is the one that
best evaluates a change touching it. This relationship between team structure and code
structure was discussed on its institutional side in the Process, Team, and Delivery
course; it is not repeated here. The only thing measured in this lesson is review's own
numbers.

## The Approval Rule: How Many Approvals and From Whom

An ownership mapping says who gets called; an **approval rule** says how many approvals
are required. The two settings are independent of each other and produce four
combinations: no ownership and a single approval; ownership and a single owner's approval;
ownership and more than one owner's approval; ownership with an open approval added
alongside the owner's approval.

All of these settings are expressed **in number of people**, and the course's measure is
not number of people. A single required reviewer carries one axis; two required reviewers
do not have to carry two axes. Because an ownership rule is written by path, owners of the
same path most often carry **the same viewpoint**: they know the section's implementation.
Raising the approval count in this case adds people, not axes.

The rule has a third setting too, and it stays outside the measurement: **does the
author's own approval count?** When an owner writes to their own section, the rule shows
them as both author and required reviewer. Approval in this case adds no view — the only
view already looking at the change is the one that wrote it. In the measure's language:
self-approval adds **zero axes**. The rule excluding this does not change a row in the
table, but it protects the table's validity; if not excluded, every row would show the
approval count in the first column higher than it really is.

The measurement tests exactly this point. The course's second claim said **adding a
reviewer does not find defects; adding an axis does.** An ownership rule is the cleanest
example of a mechanism that adds reviewers without adding an axis; the claim shows up here
**in reverse.**

The measurement's assumptions:

- **TF15** — The shared setup's 600-line change and 24 defects are used as they are; the
  class distribution and the oracle are the course's constant.
- **TF16** — The axis the owner looks at is **implementation**. Because ownership is
  assigned by path and the path's owner is the role that knows the section's
  implementation, this is the default view.
- **TF17** — More than one owner of the same path carries **the same axis**. This is not
  a claim about a person, it is the result of the rule: the rule chooses the path, the
  path chooses the view.
- **TF18** — An open approval is a review with no axis assignment restriction and it
  looks at all five axes. The number of axes a rule opens is the union of the axes in the
  assignment.
- **TF19** — Attention is fixed at **12 chunks**, and 600 lines is 12 chunks; no chunk
  goes unread. The only thing that changes in this measurement is the axis assignment.
- **TF20** — The round cycle comes from the shared setup and writes **6 units** of wait
  per round. Approval count does not directly change the round; the round is determined
  by the defect count found in the first round.
- **TF21** — The set's resolution at 24 defects is **1/24**; the smallest measurable
  difference at the axis scale is one axis, and axes are not equal in size.

## Measurement

```python
"""Code ownership and required reviewers: the approval rule's axis diversity.

Part 1 - six approval rules; required reviewer count against axes looked at.
Part 2 - defects standing in axes no owner looks at.
"""
SEED = 20260815
AXES = ("interface", "implementation", "test", "documentation", "style")
UNWRITTEN = "unwritten requirement"
CLASSES = AXES + (UNWRITTEN,)
ATTENTION, CHUNK = 12, 50


def rng(seed):
    d = seed % 2147483646 + 1

    def draw(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return draw


def change(lines, defect_count=24, seed=SEED):
    draw, defects = rng(seed), []
    chunk_count = max(1, lines // CHUNK)
    for i in range(defect_count):
        defects.append({"no": i + 1, "class": CLASSES[draw(6)],
                         "chunk": draw(chunk_count)})
    return {"lines": lines, "chunks": chunk_count, "defects": defects}


def review(d, axes, attention=ATTENTION):
    read = set(range(min(attention, d["chunks"])))
    return {k["no"] for k in d["defects"]
            if k["class"] in axes and k["chunk"] in read}


def panel(d, assignments, attention=ATTENTION):
    found = set()
    for axes in assignments:
        found |= review(d, axes, attention)
    return found


def review_rounds(found, defect_count, wait_per_round=6):
    remaining, rounds, wait = defect_count - len(found), 1, wait_per_round
    while remaining > 0 and rounds < 6:
        rounds += 1
        wait += wait_per_round
        remaining -= max(1, len(found) // 2)
    return rounds, wait, max(0, remaining)


FULL = set(AXES)
OWNER = {"implementation"}          # the single axis the owner looks at
RULES = {
    "no ownership, one approval": [FULL],
    "one required reviewer": [OWNER],
    "two required reviewers": [OWNER] * 2,
    "three required reviewers": [OWNER] * 3,
    "required reviewer + open approval": [OWNER, FULL],
    "three owners, separate axes": [{"interface"}, OWNER, {"test", "documentation"}],
}
d = change(600)
distribution = {s: sum(1 for k in d["defects"] if k["class"] == s) for s in CLASSES}
print("class distribution: " + ", ".join(f"{s} {n}" for s, n in distribution.items()))
print()
print(f"{'approval rule':<34s} {'appr':>4s} {'axes':>5s} {'found':>7s} "
      f"{'missed':>5s} {'unwritten':>10s} {'rounds':>3s} {'wait':>7s} "
      f"{'remaining':>5s}")
for name, assignment in RULES.items():
    b = panel(d, assignment)
    missed = [k for k in d["defects"] if k["no"] not in b]
    unwritten = sum(1 for k in missed if k["class"] == UNWRITTEN)
    rounds, wait, remaining = review_rounds(b, len(d["defects"]))
    axes = set().union(*assignment)
    print(f"{name:<34s} {len(assignment):4d} {len(axes):5d} {len(b):7d} "
          f"{len(missed):5d} {unwritten:10d} {rounds:3d} {wait:7d} {remaining:5d}")

print()
print(f"{'approval rule':<34s} {'axes not covered':>16s} {'defects standing there':>17s}")
for name, assignment in RULES.items():
    axes = set().union(*assignment)
    uncovered = [a for a in AXES if a not in axes]
    standing = sum(1 for k in d["defects"] if k["class"] in uncovered)
    print(f"{name:<34s} {len(uncovered):16d} {standing:17d}")
```

```
class distribution: interface 3, implementation 6, test 3, documentation 3, style 7, unwritten requirement 2

approval rule                      appr  axes   found missed  unwritten rounds    wait remaining
no ownership, one approval            1     5      22     2          2   2      12     0
one required reviewer                 1     1       6    18          2   6      36     3
two required reviewers                2     1       6    18          2   6      36     3
three required reviewers              3     1       6    18          2   6      36     3
required reviewer + open approval     2     5      22     2          2   2      12     0
three owners, separate axes           3     4      15     9          2   3      18     0

approval rule                      axes not covered defects standing there
no ownership, one approval                        0                 0
one required reviewer                             4                16
two required reviewers                            4                16
three required reviewers                          4                16
required reviewer + open approval                 0                 0
three owners, separate axes                       1                 7
```

## The Second Claim in Reverse

The table's second, third, and fourth rows are identical. Approval count rises as **1**,
**2**, **3**; found stands at **6**, missed at **18**, rounds at **6**, wait at **36**, and
remaining at **3**. Triple the approvals does not get even one more defect found.

The course's second claim said, going forward, "adding an axis finds." Here the claim
reads in reverse: **adding a reviewer without adding an axis finds nothing.** Adding
people through an ownership rule is not free of cost either — waiting on three people's
approval produces a real wait; even though it does not show up in round count in the
measurement, the item collects three separate signatures in sequence.

The first row gives the comparison. A review with no ownership rule, single-approval, and
no axis restriction finds **22**, misses **2**, and closes in **2 rounds / 12 wait** —
more than **three times** what the three required owners found, at a third of the wait.
Approval count drops to one and the result improves; because what changed is not the
count, it is the **axis count**: from **1** to **5**.

The fifth row shows the combination that rescues the rule. The required owner stays, and
an approval with no axis restriction is added alongside: found **22**, missed **2**,
rounds **2**, wait **12**. The ownership rule's value is preserved — the role that knows
the section still looks — and the axis gap closes. The rule becomes this: **a required
reviewer is a guarantee of one axis, not a guarantee of coverage.**

The fifth row's approval count is **2**, more than the second row's **1**; despite this,
rounds drop from **6** to **2**, wait from **36** to **12**. Adding an approval shortened
the chain this time — because the added approval brought a new axis. The same operation
working in both directions shows why person count has no place in the measure: approval
count alone is neither a good nor a bad sign, it cannot be read without the axis column
next to it.

The sixth row produces the same result a different way. Three owners, because they own
three separate sections, look at three separate axes: found **15**, rounds **3**, wait
**18**. Three approvals are still three approvals, but the axes are **4** instead of
**1**, and the number changes accordingly. **An ownership rule's quality is not in how
many owners it calls, it is in how many separate axes the owners it calls represent.**

## The Gap in Ownership

The second table reads the same rows from a different question: how many defects stand in
axes the rule never covers at all?

In single-axis ownership rules, **4** axes are open and **16** defects stand there. These
are not a subset of the missed; **16** of the missed **18** come from here, the remaining
two are the **unwritten requirement** class. So in narrow ownership, nearly all of the
missed stand in axes **no one is responsible for.**

How this number is read matters. The **16** defects are not defects looked at and not
seen; they are defects in axes never looked at at all. The gap between the two determines
where the process should touch: for a defect looked at and not seen, attention or change
size is discussed; for an axis never looked at, **the rule itself** is changed. Applying
the same fix to two different problems solves neither.

In the sixth row, which looks at three separate axes, the open axis drops to **1** and the
defects standing there to **7**. The one open axis is **style**, and there are **7**
defects there — the most crowded class in the set. This does not say that adding an owner
for the style axis to the ownership rule would give the single biggest individual gain;
the crowded class is the class that can be closed the cheapest, and closing it does not
need a person. That discussion belongs to the review axis topic's own lesson.

The last column's fixedness underlines this again: in all six of the six rules, **2**
**unwritten requirement** defects are missed. However broadly the ownership rule is
written, no one can be the owner of what was never written.

## The Unmeasured Side of the Gate

The measurement deliberately leaves one thing out, and this limit has to be written:
**the required reviewer's availability.** The round cycle writes a fixed wait onto every
round and does not look at how many people that wait is collected from. In reality a gate
binds the queue to a single role; if that role is busy, the item stands in the
`in review` column and the chain's most volatile link lengthens further.

This effect does not show up in the table, but it changes how the table should be read.
The second, third, and fourth rows are identical in terms of found; **they are not in
terms of wait.** A rule collecting three signatures in sequence takes longer than a rule
producing the same result with one signature, and the measurement does not record this
gap. So the table's **36** units, for the rule with three required reviewers, is a
**lower bound.**

The second unmeasured side is that the rule's scope grows with file count. A broad path
prefix binds every change to the same role; that role becomes the gate for the entire
queue. It is invisible in the measurement, because the measurement looks at a single work
item. It is visible in the chain, because the number of items waiting in the same role's
queue swells the `queued` column.

The third side appears in the rule's **absence**. A repository with no ownership rule
gets the first row — **22** found — but only if someone who actually looks at that change
alone steps forward. The rule does not guarantee axis diversity; it guarantees that a look
will exist. The measurement counts the look that exists, not the one that does not.

## Summary

- **Code ownership** is a mapping that binds a file path to a role; matching roles become
  **required reviewers** and their approval works like a gate.
- Raising required reviewer count from **1** to **3** leaves found fixed at **6**, rounds
  at **6**, and wait at **36**: adding a reviewer without adding an axis finds nothing.
- A **single** approval with no axis restriction finds **22** on the same change and
  closes in **2 rounds / 12 wait**; the gap comes not from person count but from axis
  count climbing from **1** to **5**.
- Adding an open approval alongside the required owner gives both: found **22**, open
  axes **0**. Ownership is a guarantee of one axis, not a guarantee of coverage.
- In narrow ownership, **4** axes stay open and **16** defects stand there; these are not
  defects looked at and missed, they are defects never looked at, and their remedy is the
  rule itself.
- In all six of the six rules, **2** **unwritten requirement** defects are missed; no one
  can be assigned as the owner of what was never written.

## Next Step

Up to this point, the rules of the flow were built with text: the guide was a text,
ownership was a mapping text. There is also a surface these rules run on — the
capabilities where the work item is recorded, the queue is seen, discussion attaches to
the change, merged work gets published, and the published becomes consumable. The next
lesson takes up these capabilities **at the class level** and asks each one a single
question: **which transition's wait does it shorten?** A capability with no answer is a
capability with no place in the chain; and whether any of them touches the number of
defects found will show up in the same measurement.
