Route pull request reviews to the owning application team - #10228
Open
Jesper Schulz-Wedde (JesperSchulz) wants to merge 1 commit into
Open
Route pull request reviews to the owning application team#10228Jesper Schulz-Wedde (JesperSchulz) wants to merge 1 commit into
Jesper Schulz-Wedde (JesperSchulz) wants to merge 1 commit into
Conversation
Jesper Schulz-Wedde (JesperSchulz)
temporarily deployed
to
triage
August 13, 2026 11:26 — with
GitHub Actions
Inactive
Jesper Schulz-Wedde (JesperSchulz)
force-pushed
the
jesperschulz-codeowners-team-review-routing
branch
2 times, most recently
from
August 13, 2026 12:07
2952019 to
6c57b3c
Compare
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
Jesper Schulz-Wedde (JesperSchulz)
force-pushed
the
jesperschulz-codeowners-team-review-routing
branch
from
August 13, 2026 12:19
6c57b3c to
883b257
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
The default
CODEOWNERSentry 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/BCAppsTriage→plugins/triage/skills/triage/scripts/ownership/ownership-rules.jsmicrosoft/BCAppsTriage→plugins/triage/skills/triage/scripts/ownership/ownership-resolver.jswhich are in turn seeded from the Business Central Ownership Matrix.
That agent already decides which team owns an issue or pull request. Deriving
CODEOWNERSfrom 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
CODEOWNERSresolves last-match-wins and the resolver it mirrors is itself layered:Sales/Reminder,Purchases/Payables)PowerBIReportsfoldersDemoData/<Area>folder rather than the appWorth 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
CODEOWNERSand Markdown only.)CODEOWNERS; validated by exhaustive simulation instead.)What I tested and the outcome
I implemented a
CODEOWNERSresolver (last-match-wins, gitignore-style patterns) and ran the triage agent's ownteamFromPath()(plus its path-based overrides) over every one of the 46,437 tracked files, then compared.mainWhere the final file differs from the resolver, it is always a specialized rule lower in the file deliberately taking precedence:
app.jsonto the app team (877 files), the Copilot and Developer Tools carve-outs inside the System Application (170),dotnet.alto App Security (33),*.Entitlement.alto Integration (24), plus*.ps1,/buildand/.githubto 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:
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
CODEOWNERSrequests 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, andCODEOWNERScan 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-rulesetsetsrequire_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):
Integration is the biggest change here: it previously owned almost nothing in
CODEOWNERSand 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
CODEOWNERSgenuinely 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);CODEOWNERShas owned the whole app as Finance since Add App Finance CODEOWNERS entries for owned apps #9752. 112 files.ServiceManagement—appFolderRulessays SCM;CODEOWNERSsays Finance. 2 files.Three findings for whoever maintains
ownership-rules.js(none of them blocking this pull request):Microsoft.Inventory.Intrastatand object id 4810, explicitly so "PRs and issues agree" — butappFolderRules.IntrastatsaysFinance. A path-only consumer such asCODEOWNERScannot see the namespace, so it routes Intrastat to Finance. A path rule would close the gap.BaseApp/Projects—baseAppAreaRules.ProjectssaysFinance, whilesubAreaToTeam['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.Integration.ownership-rules.jscontains 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".CODEOWNERSmatches 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.jsinBCAppsTriageinstead. The generator reads those modules directly, so it belongs alongside them inBCAppsTriagerather than in this repo.Follow-up requiring a repository administrator (not in this PR): the
Official Branchesruleset still has afile_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-1is the correct Finance group (ad365-bc-financeteam also exists, with 6 members).