Lesson 13 / 15
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.
Contents
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.
# 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
"""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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.