Split auto-generated principles sync MR per SSOT-owning team
Problem
The weekly principles-sync auto-MR currently routes all affected distilled files through one MR (e.g. !235635 (merged) with 23 files changed). The only CODEOWNERS rule that fires on /.ai/ points at 4 AI-tooling DRIs — so the merge can proceed with 1 approval from any of 4 people, regardless of which domains the distilled content touched.
This is too coarse for cross-domain content. Each distilled file in .ai/principles/distilled/<name>.md is a domain-specific checklist derived from an SSOT doc owned by a specific team:
security.md→@gitlab-com/gl-security/appsecdatabase-migrations.md→@gitlab-org/maintainers/databasegraphql.md→@gitlab-org/maintainers/rails-backendfrontend-vue.md→@gitlab-org/maintainers/frontendqa-rspec.md→@abdwdd @alexpooley- etc. (~10–11 distinct teams across the current 23 principles)
AppSec review on the gem (see https://gitlab.com/gitlab-com/team-member-epics/access-requests/-/issues/43931) flagged this as the right thing to fix.
Proposal
Refactor Sync#publish and Sync::AutoMr#create_branch_and_mr to fan out by SSOT-team owner:
- Add an
owner_team:field per principle in.ai/principles/manifest.yml. Primary owner only; secondary teams go into the MR description as "request review from" mentions. - Group affected principles by
owner_team. For each unique team:- Create branch
docs-sync/principles-<date>-<team-slug>fromorigin/master. - Write only that team's distilled files.
- Push and open one MR per team. CODEOWNERS routes the approval correctly because each MR only touches files the SSOT team owns.
- Create branch
- AGENTS.md, CLAUDE.md, and
.{agents,claude}/skills/gitlab-coding-principles/SKILL.mdare global (they embed the full routing table for all principles). Carve those out into a separate tooling MR routed to AI-tooling DRIs, since SSOT teams have no stake in their content. - Same-day re-run idempotency:
find_open_mr_iidbecomes per-team; the script looks up each team's open MR and updates in place.
Implementation outline
- Manifest schema: add
owner_team:per principle. Add ateam_slug:for the branch name suffix (derived from team handle, e.g.databasefrom@gitlab-org/maintainers/database). - Sync::AutoMr#create_branch_and_mr: replace the single-branch + single-MR flow with a per-team fan-out. Push N branches (one per affected team) plus 1 tooling branch.
- Sync::AutoMr#create_mr: parameterise the title template by team. Add per-team description content (only that team's principles in the per-principle sections).
- Per-team idempotency:
find_open_mr_iidruns once per team branch. Same-day re-runs update each team's existing MR in place rather than creating duplicates. - Specs: new fixtures covering multi-team affected sets, tooling-MR separation, per-team idempotency on re-run, edge cases (only one team affected, no team-owner specified, etc.).
Scope
This issue tracks MR-C, stacked on top of MR-A (!235272 (merged)) and independent of MR-B (!235014). The CI YAML doesn't change.
Out of scope
- Changing CODEOWNERS for the gem itself.
- Reworking how distillation triggers (still weekly schedule, still through the Duo Workflow API).
- Cross-team principles where the SSOT spans multiple owners: primary owner gets the MR; secondary teams are mentioned in description.
Related
- AR sign-off conditions: https://gitlab.com/gitlab-com/team-member-epics/access-requests/-/issues/43931
- MR-A (gem + flow): !235272 (merged)
- MR-B (CI job): !235014
- Example under-routed auto-MR: !235635 (merged)
- Parent epic: gitlab-org&21742