Chapter 5 of 7 · 9 min read
Deliberate resolution means the losing layer says nothing
On this page
The release notes for v3.11.0 list seven changes. The milestone had twelve.
$ git log --oneline v3.10.0..v3.11.0
9f2ab41 feat(api): add cursor pagination to /orders
1c88de0 [PLAT-441] tighten rate limiter window
77b1e05 fix(api): handle null customer on refund
40de993 [PLAT-447] drop the legacy webhook shim
2ea6c7c feat(billing): support partial captures
b1d7f30 [PLAT-452] cache tenant lookups
...The changelog generator parses conventional commits — subject lines shaped type(scope): summary, so that feat, fix and the rest can be sorted into sections by a machine — and skips anything it cannot classify, so the five commits carrying the team's [PLAT-nnn] prefix vanished from the notes, including the webhook shim removal that a customer integration depended on. Both commit styles were produced by the same agent, in the same repository, during the same fortnight, by people following the instructions in front of them. The organisation layer requires conventional commits across every repository. The project layer, written by a team whose Jira automation keys off the ticket prefix, requires [PLAT-nnn]. Both are in the assembled context on every run, and the model has been picking.
The cost landed three weeks later, when a partner's integration broke against the removed webhook shim and their engineers pointed out, correctly, that the removal appeared in no release note they had been given. The support thread ran four days. Counting the alternation afterwards took one command — git log v3.10.0..v3.11.0 --pretty=%s | grep -cE '^[a-z]+(\(.+\))?:' returned 7 of 12 — and that number had been available at any point in the preceding fortnight to anyone who thought to look. Alternating output is unusually cheap to detect compared with most agent misbehaviour, because commits are already a structured record of what the agent decided.
Incidental resolution ships on someone else's release schedule
The tempting repair is to leave both rules in place and find the arrangement that makes the right one win — move the org text later in the assembly, or shorten the project text, or add "always" to the sentence you prefer. Each of these can move the observed rate, and none of them is a decision that anything in the system records.
What you would be relying on is load order, which is a property of the host rather than of your repository. Hosts reorder these things for ordinary reasons: a release that starts placing organisation policy after project files so that centrally distributed security guidance is not diluted, a change to how nested files are concatenated, a new summarisation step that compresses the middle of a long assembly. None of that appears in your repository's history, so when the commit prefixes flip back in March the diff that caused it is in a changelog you do not read, and the bisect you run against your own repository finds nothing.
The 4-out-of-5 measurements from chapter 3 are what this looks like before it breaks. A team that measures "the project layer wins on commit style, four times in five" and treats that as settled has recorded a behaviour, not a rule, and the fifth run is already telling them the outcome is not owned by anyone.
Somebody owns commit-message policy
The deliberate version starts with a question that has nothing to do with configuration files: which of these two outcomes does the organisation actually want, and who is entitled to decide? That is a five-minute conversation between the platform team and the team that wrote the prefix rule, and it has three plausible answers.
| Resolution | What changes | What it costs |
|---|---|---|
| Org owns it; conventional commits everywhere | Team rewires Jira automation to parse the commit body or a trailer | Half a day of automation work, once |
| Team owns it; this repo is exempt | Org policy names the exemption; changelog generator is configured per repo | Release notes for this repo need a different generator config |
| Both, merged | A single stated format such as feat(api): [PLAT-441] … that satisfies both parsers | Ugly commit titles, and someone has to verify both tools accept it |
Any of the three is defensible. What is not defensible is leaving the choice to whichever paragraph the model weighted higher on a Tuesday, because that is the same as having no policy while believing you have two.
Once the decision is made, the edit is the part teams get wrong. The losing layer has to become silent on the subject — not softened, not qualified with "prefer", not moved lower in the file, but removed. A sentence that says "we generally use [PLAT-nnn] prefixes, though the org standard is conventional commits" is still two rules in the context, and the model still has to arbitrate, now with the added hint that the author was uncertain.
<!-- platform/CLAUDE.md — before -->
## Commits
Prefix every commit with the Jira ticket: `[PLAT-441] tighten rate limiter`.
This is how our board picks up work automatically.<!-- platform/CLAUDE.md — after, org owns the policy -->
## Commits
Use conventional commits, per the organisation standard.
Put the Jira ticket in a trailer so the board still picks it up:
feat(api): add cursor pagination to /orders
Refs: PLAT-441The project layer now says nothing that competes. It still speaks about commits, because there is genuine local information — where the ticket reference goes, in this case a trailer, meaning a Key: value line at the foot of the commit message that git and the board's parser both read separately from the subject — and that information does not contradict the org rule, it extends it. Silence is per subject, not per file, and a team that reads "delete the losing statement" as "delete the file" loses the twenty things in it that were never contested.
The other half of the edit belongs to the winning layer, and it is the half nobody does. Whoever now owns commit-message policy should be able to answer why the rule exists when the next team objects to it, so the org text is worth a clause naming the changelog generator that consumes it. A rule with its reason attached survives contact with the team that has a good argument against it; a rule without one gets locally overridden again in nine months by someone who reasonably assumes it was arbitrary.
Record the decision somewhere with a date and a name on it. A deleted sentence leaves no trace of having been considered, so the team that loses the argument this quarter has nothing to point at when a new engineer reads the org standard, finds it inconvenient, and adds the prefix rule back to the project file. Three lines in the repository's decision log — the two rules, who owns the subject, the date — cost nothing and turn a re-litigation into a link. This is also the artefact that makes the resolution auditable later: without it, the only evidence that anyone chose is an absence, and an absence is indistinguishable from nobody having noticed.
When the local rule is an exception rather than a rival
Maya's changeset problem from chapter 1 looks like the same shape and resolves differently. The root rule requiring a changeset for user-visible changes is correct for the thirty published packages in the monorepo, and the statement that internal-tools is never published is a fact about the world rather than an opinion competing with the root's opinion. Neither file can go silent: deleting the root rule breaks the changelog for thirty packages, and deleting the local rule leaves Maya deleting changeset files by hand.
The resolution here is a single statement, in one file, that names the exception explicitly, so that the relationship between the two cases is written down rather than inferred. Two placements work and they fail differently.
<!-- Option A — the root file owns the exception list -->
## Release process
Every user-visible change ships with a changeset.
Exceptions: `packages/internal-tools/` and `packages/e2e-fixtures/` are
never published to the registry and take no changesets.<!-- Option B — the nested file names the rule it is excepting -->
## This package
`internal-tools` is never published to the registry.
The repository's changeset requirement does not apply to files under
`packages/internal-tools/`. Do not create changeset files here, even for
user-visible changes to this package's CLI.Option A keeps the whole policy in one readable place and puts the exception in front of every session in the repository, at the cost of a root file that grows a line per exception and a maintenance burden on a team that does not own those packages. Option B keeps the knowledge next to the package that has it, at the cost of the exception being invisible to anyone reading the root file and, under nearest-file discovery, invisible to the session too. The choice depends on how many exceptions there are and who is likely to add the next one; with two exceptions, option A is easier to keep true, and by the time there are nine, nobody at the root can maintain it.
What both options share is the thing that makes them deliberate: exactly one file states the relationship between the two rules, in a sentence that mentions both. Chapter 1's version of the nested file — "this package is not published; skip changesets" — never mentioned the root requirement, which is why the model had to guess whether it was an exception or a contradiction. Naming the rule you are excepting costs eleven words and removes the guess.
| Situation | Deliberate resolution | Failure if you skip it |
|---|---|---|
| Two layers want different outcomes for the same code | Pick an owner; the loser deletes its statement | Load order decides, and load order ships upstream |
| Local case is genuinely outside the general rule | One file states the exception and names the rule | Model treats a fact as a rival opinion, alternates |
| Both outcomes are required simultaneously | One merged format, verified against both consumers | Each tool works on roughly half the commits |
Ordering influences, it does not enforce
The reflex fix — make the org layer load last so it always wins — is worth taking seriously enough to say precisely why it fails, because it is the answer most people give and it is almost right.
It fails on the guarantee. Position affects how a model weights instructions, and later text does tend to carry more weight, so moving the org rule last will move the observed rate; on the commit-prefix case it might take conventional commits from 7 in 12 to 11 in 12. It will not take it to 12 in 12, because nothing in the mechanism forbids the other sentence from winning, and the twelfth commit is still an unclassifiable line in the changelog. The security team that asked for non-overridable org rules and was told "we put them last now" has been given an unquantified improvement and hears a guarantee. That gap is worse than the original problem, since the original problem was at least visible.
It also fails on scope, because ordering is global. Making the org layer authoritative by position does it for every org instruction, including the forty that are style preferences and the ones that are genuinely stale, and it inverts the property that made local files useful — a package that has a real reason to differ now loses that argument by construction. The one rule that needed protecting could not be protected selectively, so everything got protected instead.
There is a third failure that only shows up later. Once the arrangement depends on order, the order becomes load-bearing, and every subsequent edit to any layer is a change to a system nobody has documented. Someone adds 600 tokens to the project file, the org section is now further from the end in relative terms, and the rate drifts back without a single line of policy having changed.
The honest position is that a text-layer resolution gives you a strong default and no guarantee, which is sufficient for commit prefixes and insufficient for a security review requirement. Deciding the owner and making the loser silent is the whole of what configuration can do. Anything that has to hold when someone is arguing against it — or when a model happens to weight a paragraph differently on one run out of forty — needs a mechanism that does not read as English, and that is what the next chapter builds.
Scoping and precedence — read the current version at https://daviddominguez.dev/learn/en/scoping-and-precedence/deliberate-resolution-means-the-losing-layer-says-nothing