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/appsec
  • database-migrations.md@gitlab-org/maintainers/database
  • graphql.md@gitlab-org/maintainers/rails-backend
  • frontend-vue.md@gitlab-org/maintainers/frontend
  • qa-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> from origin/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.
  • AGENTS.md, CLAUDE.md, and .{agents,claude}/skills/gitlab-coding-principles/SKILL.md are 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_iid becomes per-team; the script looks up each team's open MR and updates in place.

Implementation outline

  1. Manifest schema: add owner_team: per principle. Add a team_slug: for the branch name suffix (derived from team handle, e.g. database from @gitlab-org/maintainers/database).
  2. 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.
  3. 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).
  4. Per-team idempotency: find_open_mr_iid runs once per team branch. Same-day re-runs update each team's existing MR in place rather than creating duplicates.
  5. 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.