Skip to content

Route pull request reviews to the owning application team - #10228

Open
Jesper Schulz-Wedde (JesperSchulz) wants to merge 1 commit into
mainfrom
jesperschulz-codeowners-team-review-routing
Open

Route pull request reviews to the owning application team#10228
Jesper Schulz-Wedde (JesperSchulz) wants to merge 1 commit into
mainfrom
jesperschulz-codeowners-team-review-routing

Conversation

@JesperSchulz

@JesperSchulz Jesper Schulz-Wedde (JesperSchulz) commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What & why

The default CODEOWNERS entry points every path at @microsoft/dynamics-365-business-central, which has 123 members. Every pull request therefore requests review from 123 people — a request addressed to everyone is owned by nobody.

The measurable consequence today: 149 of 233 ready pull requests have no review decision at all. GitHub itself cannot say who owes the review, so no filter, query or report can either.

Where the mapping comes from

The team rules are generated from the routing data the BCApps triage agent already uses to classify issues and pull requests:

  • microsoft/BCAppsTriageplugins/triage/skills/triage/scripts/ownership/ownership-rules.js
  • microsoft/BCAppsTriageplugins/triage/skills/triage/scripts/ownership/ownership-resolver.js

which are in turn seeded from the Business Central Ownership Matrix.

That agent already decides which team owns an issue or pull request. Deriving CODEOWNERS from the same data means the team label an item gets and the reviewer GitHub requests cannot disagree — and there is no second copy of the mapping to drift.

Structure

The generated block is ordered broad to narrow, because CODEOWNERS resolves last-match-wins and the resolver it mirrors is itself layered:

Tier Contains
1 Tree defaults — W1 apps and layers to Integration, every country tree to Finance
2 Base Application areas for W1 and every localization layer, plus the Finance sub-ledgers that sit inside SCM area folders (Sales/Reminder, Purchases/Payables)
3 Other W1 layer trees, including per-area test ownership
4 W1 apps that are not Integration, plus the per-area PowerBIReports folders
5 Demo datasets, which follow the functional DemoData/<Area> folder rather than the app
6 Documented path overrides (for example the Italian Service Declaration)
7 Reconciliation pending — see below

Worth calling out: a team appears in more than one tier. Grouping every rule for a team together reads better, but it cannot reproduce the resolver's precedence, so correctness wins.

Linked work

AB#642155 — the "team ownership" half of that slice; #9420 is the "whose turn" half.

How I validated this

  • I read the full diff and it contains only changes I intended.
  • I built the affected app(s) locally with no new analyzer warnings. (N/A: CODEOWNERS and Markdown only.)
  • I ran the change in Business Central and confirmed it behaves as expected. (N/A: no product code.)
  • I added or updated tests for the new behavior, or explained below why none are needed. (No test hook exists for CODEOWNERS; validated by exhaustive simulation instead.)

What I tested and the outcome

I implemented a CODEOWNERS resolver (last-match-wins, gitignore-style patterns) and ran the triage agent's own teamFromPath() (plus its path-based overrides) over every one of the 46,437 tracked files, then compared.

Check Result
Generated block reproduces the triage resolver's path decision 44,796 of 44,796 files (100%)
Pre-existing non-default ownership changed vs main 0 files
Files moved off the 123-member default owner 37,694

Where the final file differs from the resolver, it is always a specialized rule lower in the file deliberately taking precedence: app.json to the app team (877 files), the Copilot and Developer Tools carve-outs inside the System Application (170), dotnet.al to App Security (33), *.Entitlement.al to Integration (24), plus *.ps1, /build and /.github to Engineering Systems. That is the intended "Integration owns the System Application, with sub-sections owned by others" shape.

Cross-checked against the team labels the triage agent actually applied, over 425 labelled pull requests:

Outcome PRs Share
Single-team PR, CODEOWNERS matches the label 312 73.4%
Multi-team PR, labelled team is the dominant owner 68 16.0%
Multi-team PR, labelled team owns a minority of files 13 3.1%
CODEOWNERS never requests the labelled team 25 5.9%
No team owner at all 7 1.6%

CODEOWNERS requests the labelled team on 92.5% of pull requests. Exact one-to-one agreement is neither expected nor desirable: the agent picks a single team per pull request, whereas CODEOWNERS requests every owner of every touched file, so a pull request spanning two teams correctly gets two reviewers. The agent also reads file content (namespace, object id) which takes precedence over path, and CODEOWNERS can only ever see the path.

Re-verified after rebasing onto current main.

Risk & compatibility

This makes the three teams required approvers for their paths. microsoft-production-ruleset sets require_code_owner_review: true, so these entries gate merges — this is the change that needs your sign-off, not the file itself.

Review load from currently open, non-draft pull requests that have a team owner (187 of them, of which 53 touch more than one team and will therefore need approval from each):

Team Members Open PRs Per member
Finance 8 91 11.4
SCM 9 73 8.1
Integration 10 90 9.0

Integration is the biggest change here: it previously owned almost nothing in CODEOWNERS and now owns the System Application, Business Foundation, the migration and connector apps, and every unlisted W1 app.

Two disagreements are deliberately left unresolved. The triage rules and today's CODEOWNERS genuinely disagree in two places. Taking ownership away from a team is not something this change should do silently, so current ownership is preserved in tier 7 and the disagreement is recorded:

  • PowerBIReports — the triage agent resolves its per-area report folders to the area owner (App/Sales → SCM); CODEOWNERS has owned the whole app as Finance since Add App Finance CODEOWNERS entries for owned apps #9752. 112 files.
  • ServiceManagementappFolderRules says SCM; CODEOWNERS says Finance. 2 files.

Three findings for whoever maintains ownership-rules.js (none of them blocking this pull request):

  1. Intrastat is forced to SCM by an override keyed on the namespace Microsoft.Inventory.Intrastat and object id 4810, explicitly so "PRs and issues agree" — but appFolderRules.Intrastat says Finance. A path-only consumer such as CODEOWNERS cannot see the namespace, so it routes Intrastat to Finance. A path rule would close the gap.
  2. BaseApp/ProjectsbaseAppAreaRules.Projects says Finance, while subAreaToTeam['Projects'] and the ownership matrix both say SCM. The agent resolves SCM via object id (Job, table 167). The two data sections contradict each other.
  3. Country E-Document labels look stale. Seven of the 25 disagreements are country E-Document apps labelled Integration. ownership-rules.js contains an override routing those to Finance whose comment notes it "deliberately supersedes the earlier Harden FR Factur-X test setup against orphaned VAT posting groups #9172 decision". CODEOWNERS matches the current rule; those labels predate it and would benefit from a re-run.

Do not hand-edit the generated block. Edits are lost on the next regeneration and silently diverge from issue and pull request triage. Change ownership-rules.js in BCAppsTriage instead. The generator reads those modules directly, so it belongs alongside them in BCAppsTriage rather than in this repo.

Follow-up requiring a repository administrator (not in this PR): the Official Branches ruleset still has a file_patterns: ["*"] rule requiring one approval from the same 123-member team. Until that is replaced with the same team patterns, the merge gate stays anonymous even though the request is now routed correctly. It should follow this PR, so routing is proven before the gate is tightened.

Reversible: revert the commit and resolution returns to exactly the current behaviour.

Reviewers: please sanity-check the tiers for your own area, confirm the two tier-7 disagreements, and confirm d365-bc-finance-1 is the correct Finance group (a d365-bc-finance team also exists, with 6 members).

@github-actions github-actions Bot added Other GitHub request for other area than SCM, Finance or Integration Ownership: Needs Review Ownership is Other, low confidence, or needs manual correction labels Aug 13, 2026
@github-actions github-actions Bot added this to the Version 29.0 milestone Aug 13, 2026
@JesperSchulz
Jesper Schulz-Wedde (JesperSchulz) force-pushed the jesperschulz-codeowners-team-review-routing branch 2 times, most recently from 2952019 to 6c57b3c Compare August 13, 2026 12:07
The default CODEOWNERS entry pointed every path at a 123-member team, so
review requests were addressed to everyone and owned by nobody. 149 of 233
ready pull requests had no review decision as a result.

Generate the team rules from the routing data the BCApps triage agent already
uses to classify issues and pull requests (microsoft/BCAppsTriage,
plugins/triage/skills/triage/scripts/ownership/ownership-rules.js and
ownership-resolver.js), which are themselves seeded from the Business Central
Ownership Matrix. Deriving CODEOWNERS from that same data means the team label
an item gets and the reviewer GitHub requests cannot disagree.

The generated block is ordered broad to narrow, mirroring the resolver: tree
defaults, Base Application areas, other W1 layer trees, W1 app exceptions, demo
datasets by functional area, and documented path overrides. A team therefore
appears in more than one tier; grouping by team would read better but cannot
reproduce the resolver's precedence.

Verified over all 46,437 tracked files: the generated block reproduces the
triage resolver's path decision for 44,796 of 44,796 files it can place, and no
pre-existing non-default ownership changes. 37,694 files move off the default
owner. Where the final file differs from the resolver it is always a
specialized rule lower in the file deliberately taking precedence (app.json,
*.ps1, /build, dotnet.al, *.Entitlement.al and the Copilot and Developer Tools
carve-outs inside the System Application).

Two genuine disagreements between the triage rules and today's CODEOWNERS
(PowerBIReports per-area folders and ServiceManagement) are recorded in a
reconciliation tier that preserves current ownership rather than silently
reassigning it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e3890caa-8e8e-4c8f-8506-5a22d422a0e0
@JesperSchulz
Jesper Schulz-Wedde (JesperSchulz) force-pushed the jesperschulz-codeowners-team-review-routing branch from 6c57b3c to 883b257 Compare August 13, 2026 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Other GitHub request for other area than SCM, Finance or Integration Ownership: Needs Review Ownership is Other, low confidence, or needs manual correction

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant