coance configuration
This is the specification of coance.yml, the file that tells coance how
carefully each file in a repository must be reviewed and who may approve it.
Status: draft specification. Nothing here is implemented yet.
File
- Name: exactly
coance.yml. Other spellings (COANCE.yml,coance.yaml) are a config error, so a misnamed file is never silently ignored. - Location: the first one found of
.github/coance.yml,coance.ymlat the repository root, anddocs/coance.yml, the same search order GitHub uses forCODEOWNERS. If more than one exists, the check output warns about it. - Branch: coance reads the file from the pull request’s base branch, so a pull request cannot change the rules that apply to itself.
- Top-level keys:
levels(required),rules(required) andtemplate(optional).
levels
A level says how carefully a file must be reviewed. There are no built-in levels: each repository defines its own. Levels are listed strictest first, and that order decides which level wins when rules overlap.
| Setting | Values | Default | Meaning |
|---|---|---|---|
description |
markdown | required | What a reviewer must do. Shown in each file’s thread, in the summary comment and in the app. |
approvals |
1–10 | 1 |
How many different eligible people must approve each file. |
self_review |
true, false |
true |
Whether the pull request author’s own approval counts. |
self_review defaults to true because the author of an AI-assisted pull
request is often the right person to review the generated code. Set it to
false on levels that need a second person.
rules
rules.from says where the rules come from: coance (the default) or
codeowners.
from: coance
Rules live under rules.paths, with one entry per level. Each level uses one
of two forms:
- A map of path to owners: only those owners can approve.
- A list of paths: anyone with write access can approve.
Mixing the two forms within one level is a config error.
- Path syntax: paths use
CODEOWNERSsyntax. - Overlaps: when a file matches several levels, the strictest wins,
meaning the one listed first in
levels. UnlikeCODEOWNERS, the order of the rules does not matter, so moving a rule can never quietly loosen review.
from: codeowners
Levels are assigned by annotation comments in CODEOWNERS, and owners come
from the CODEOWNERS lines themselves. GitHub ignores comments, so the
annotations do not change how CODEOWNERS works.
# coance: critical
/lib/payments/ @solid-software/payments
- Scope:
# coance: <level>applies to the lines that follow it, up to the next# coance:line.# coance: noneends a block. - Overlaps: the last matching line wins, as in
CODEOWNERS. The line that decides a file’s owners also decides its level. - Not allowed in this mode:
rules.pathsincoance.ymlis a config error.
Files that need no review
- Unmatched files: files no rule matches, which in
codeownersmode includes files whose matching line has no annotation. - Generated and vendored files: files marked
linguist-generatedorlinguist-vendoredin.gitattributes, whatever rule matches them. This is the attribute GitHub already uses to collapse generated files in diffs. These files are also left out of the metrics.
Who can approve
- Owners: if a file’s rule names owners, only those owners can approve it. If it names none, anyone with write access can.
- The author: the pull request author counts only on levels with
self_review: true. The author is the GitHub user who opened the pull request, not whoever wrote the commits. - Teams: a team owner is satisfied by any one of its members.
- Repeat approvals: a person counts once per file, however many times they approve it.
- Review requests: with
from: coance, coance requests reviews from the owners. Withfrom: codeowners, GitHub already does.
In the pull request
File threads
coance opens one review thread on each file that needs review, attached to
the file in “Files changed” and written from template. An eligible
reviewer approves the file by resolving the thread or replying
/approve in it.
Summary comment
coance posts one comment in the pull request conversation and keeps it updated in place. It shows:
- overall progress, for example “3 of 5 files approved”
- a table of the files that need review: path, level, approvals done out of approvals needed, and who approved
- each level’s description, in a collapsible section
- the lines changed in the pull request, in total and per level
template
The text of every file thread. One template is used for all levels; the
level’s description is what varies between them.
| Placeholder | Value |
|---|---|
{level} |
The level’s name |
{description} |
The level’s description |
{approvals} |
Approvals required |
{owners} |
Eligible reviewers as @mentions, or “anyone with write access” |
{file} |
The file path |
{lines} |
Lines changed in this file |
If template is left out, coance uses a built-in default much like the one
in example 2.
Approval lifecycle
- The pull request opens or updates. coance works out each file’s level and eligible reviewers, then posts or updates the threads, the summary comment and the check.
- Reviewers approve files in their threads.
- A file is done when it has
approvalsapprovals from eligible people. - New commits: a commit that changes an approved file clears that file’s approvals and reopens its thread. Files the commit does not touch keep their approvals.
Status check
coance reports a check named coance:
- Pass: every file that needs review is done.
- Pending: some files are still waiting for approval.
- Fail: the configuration is invalid. The check names the error.
Config errors:
- an unknown key, level or placeholder
- a level without a
description approvalsoutside 1–10- mixed path forms within one level
rules.pathsused withfrom: codeowners- an unknown level in a
# coance:annotation - fewer individual owners on a rule than its level’s
approvals(teams cannot be counted from the file, so rules that name a team are not checked) - a wrongly spelled file name, such as
COANCE.ymlorcoance.yaml
Metrics
Generated and vendored files are left out of every count.
- Repository: total lines of code, and lines of code per level.
- Pull request: lines changed in total, and per level.
Examples
1. Minimal: anyone can review
levels:
careful:
description: Read every changed line before approving.
quick:
description: Skim the file and check it makes sense.
rules:
paths:
careful: ["/lib/payments/", "/lib/auth/"]
quick: ["*.dart"]
2. Everything in coance.yml
levels:
critical:
approvals: 2
self_review: false
description: |
Money or auth code. Read every changed line, run it locally,
and check the tests cover the failure cases.
review:
description: Read the file as a whole. Does the change make sense?
template: |
**{level}** · {approvals} approval(s) needed · {owners}
{description}
Resolve this thread or reply `/approve` once you're done.
Review guide: https://solid.software/review-guide
rules:
from: coance
paths:
critical:
/lib/payments/: "@solid-software/payments @solid-software/security"
/lib/auth/: "@solid-software/security @solid-software/leads"
review:
"*.dart": "@solid-software/mobile"
# .gitattributes
*.g.dart linguist-generated=true
*.freezed.dart linguist-generated=true
A pull request that touches lib/payments/charge.dart, lib/ui/button.dart
and lib/ui/button.g.dart:
charge.dart(critical): gets a thread tagging payments and security. It needs two approvals, from people other than the author.button.dart(review): gets a thread tagging mobile. One approval from any member, including the author, is enough.button.g.dart: generated, so it gets no thread and is not counted.- The summary comment shows 0 of 2 files approved, and the
coancecheck stays pending until both files are done.
3. Rules from CODEOWNERS
# .github/coance.yml
levels:
critical:
approvals: 2
self_review: false
description: Read every changed line and run it locally.
review:
description: Read the file as a whole.
rules:
from: codeowners
# .github/CODEOWNERS
# No annotation: these files need no review.
*.md @solid-software/docs
# coance: review
/lib/ @solid-software/mobile
# coance: critical
/lib/payments/ @solid-software/payments @solid-software/security
/lib/auth/ @solid-software/security
The critical paths come last, so the last-match rule gives them both the
right owners and the critical level.