ADR-072: App-Repo Naming & Team Ownership
Date: 2026-06-16
Status: Accepted — Flavor A (drop the app- prefix; GitHub org Teams for ownership) is the near-term
decision. Flavor B (product-only repo names; registry-sourced team identity) is the documented ideal end
state, deferred behind two prerequisites (below).
Context
Section titled “Context”App repos are named app-<team>-<product> (e.g. app-alpha-shop). The app- prefix is doing two jobs, only
one of which is cosmetic:
- Marker / guard.
trusted-ci/build-sign.ymlrefuses to build any caller that is notapp-*— a cheap “is this a sanctioned app repo?” check. - Unspoofable team identity (supply chain).
build-signderives the team from the repo name —app-<team>-<product>→ first segment afterapp-is the team (app-alpha-shop→alpha). That team scopes the ECR push (team-<team>/<product>-*) and the cosign keyless identity. It is unspoofable because repo creation is platform-controlled (the New Product template) and the prefix is required.
Two facts constrain how far we can change this:
- The security anchor is the repo, not the name string. The real enforcement is the per-Product IAM
OIDC role (trusts that repo, scoped to
team-<team>/<product>-*— it denies a push to another team’s namespace regardless of the name), the cosign cert (bound to the repo via the GitHub Actions OIDCrepositoryclaim), and Kyvernoverify-images(gates ongithubWorkflowRepository = spec.repo). All of it is provisioned from the Product registry (gitops/products/<team>/<product>.yaml), which already carriesspec.team+spec.repo. The team-in-the-name is a derivation convenience, backstopped by the IAM role. - GitHub Teams cannot carry the supply-chain identity. Org-team membership is not in the Actions OIDC
token, so it can never appear in a cosign cert or a Kyverno
verifyImagesrule. Org Teams are the right tool for human ownership / write-access, but the cryptographic team identity will always anchor on the repo. - GitHub’s repo namespace is flat per org. Two teams cannot both own
shop. Org Teams do not add a namespace. So product-only repo names require either globally-unique product names or a GitHub org per team.
GitHub org Teams were not used at the time of writing — team identity flowed through Keycloak groups (Backstage RBAC, ADR-053),
the gitops Team CR (ADR-063), and the repo-name prefix (supply chain, ADR-050). (As-built: Flavor A has since landed — the github-teams unit now derives org Teams + repo push grants from gitops/teams/gitops/products, epic #532.) The motivation here is (a)
nicer repo names and (b) GitHub-native team ownership of app repos.
Decision
Section titled “Decision”Flavor A — accepted (near term)
Section titled “Flavor A — accepted (near term)”- Drop the
app-prefix. New app repos are named<team>-<product>(e.g.alpha-shop). The team stays in the name, so it remains parseable and — crucially — keeps repo names unique in the flat org namespace. build-signparses<team>-<product>(team = first segment) instead ofapp-<team>-<product>. No change to the trust model — team is still recovered from a platform-controlled name and backstopped by the IAM role. Theapp-*guard becomes a<team>-*shape check (the Product registry remains the real allowlist).- GitHub org Teams own the app repos. A team’s org team and its repo
pushgrants are derived from the git registries (gitops/teams/→github_team;gitops/products/→github_team_repository) by a Terragrunt unit (github-teams,infra/modules/github-teams), applied on merge — the same convergence pattern as the team’s Keycloak group (keycloak-config) and its per-Product OIDC role (github-oidc). So “create a Team → it appears in every external system” stays uniform (Keycloak and GitHub materialise at Team-CR convergence), rather than the scaffolder creating the team imperatively at form-submit time — before the Team PR is even reviewed. This is the genuinely new capability — native, auditable team write-access — layered beside (not replacing) the supply-chain identity. The unit authenticates as a dedicated GitHub App (Members: write+Administration: write, org-wide; creds in Secrets Manager) —docs/runbooks/github-ownership-app.md. Team/repo management stays platform-controlled (IaC-only), so the ownership signal is unspoofable. - The Product registry
spec.repo, the per-Product OIDC role, and Kyverno all key offspec.repoalready, so they follow the new name with no logic change — only the registry value changes.
Flavor A is low-risk (the supply-chain trust anchor is untouched in substance) and there are zero app repos under the new naming today, so it is a clean cutover with no migration.
Flavor B — ideal end state (deferred)
Section titled “Flavor B — ideal end state (deferred)”Product-only repo names (e.g. shop), GitHub org Teams as the ownership layer, and the supply-chain team
sourced from the registry / IAM role rather than the repo name. This is conceptually cleaner (the name carries
no encoded metadata) but is gated on two prerequisites that are decisions, not just code:
- P1 — product-name uniqueness. Resolve the flat-namespace collision: either enforce globally-unique product
names across teams, or adopt a GitHub org per team. Until then,
<team>-<product>is required for uniqueness. - P2 — decouple
build-signfrom the name. Source the team from the Product registry (or the GitHub Teams API) instead of parsing the name. This is surgery on the unspoofable anchor of the signing chain — it must stay app-team-unwritable — so it needs careful design, a new trusted-ci pin, and a full New-Product → build → sign → Kyverno-admit verification.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- Cleaner repo names (
alpha-shopnotapp-alpha-shop); native GitHub team ownership/write-access. - The supply-chain trust model is unchanged by Flavor A; the IAM role + cosign + Kyverno still anchor on the repo.
- Flavor B’s path is written down with its real blockers, so it’s a deliberate future step, not a vague wish.
Negative / costs
Section titled “Negative / costs”- The
app-prefix’s value as a visual/CI marker is replaced by the<team>-shape + the registry allowlist. - Org Teams add a new surface to manage: a dedicated GitHub App (
Members: write+Administration: write) and thegithubTerraform provider. Team/repo management is IaC-only (registry-derived), so the ownership signal stays platform-controlled and unspoofable. - Two naming conventions exist transiently in docs/history until references are swept.
Alternatives considered
Section titled “Alternatives considered”- Keep
app-<team>-<product>(status quo) — rejected: the prefix is redundant noise once<team>-already marks ownership. - GitHub org Teams as the supply-chain team identity — rejected: org-team membership is not in the OIDC token, so it cannot bind the cosign cert / Kyverno gate. Org Teams are ownership, not signing identity.
- Product-only names now (Flavor B immediately) — deferred: blocked on P1 (namespace collision) and P2 (build-sign decoupling); ~1 week + security-anchor risk for a name-aesthetics win.
Related work
Section titled “Related work”- Supersedes the naming half of ADR-050 (
app-<team>team derivation in build-sign) and the New Product naming in ADR-062. - Builds on ADR-067 (Team→Product→Service) and the Product registry of ADR-063 as the team↔repo source of truth.
- Complements ADR-053 (Keycloak team identity) — GitHub org Teams add the GitHub-native ownership layer that identity strategy did not cover.
- Tracking: the Flavor A epic + the Flavor B (P1/P2) issues.