From fe2830bb05efb7c64b0ede8ee2038fde9b1fd1ea Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Fri, 11 Sep 2026 22:02:45 +0800 Subject: [PATCH 1/3] fix: CODEOWNERS is not inheritable, so stop claiming it is MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous PR shipped `.github/CODEOWNERS` described as the org-wide default for every repo without its own. That is wrong, and it was wrong in the brief this work was written against, so it went in unchallenged. GitHub's list of community health files an organisation's `.github` repo can supply as defaults is CODE_OF_CONDUCT.md, CONTRIBUTING.md, discussion category forms, FUNDING.yml, issue and PR templates, SECURITY.md and SUPPORT.md. CODEOWNERS is absent from it, and the CODEOWNERS docs read the file from `.github/`, the root, or `docs/` "of the repository". The community profile API agrees: mzizi-console resolves its PR template to mzizi-dev/.github and reports codeowners as null. So the file shipped last PR governs one repo — this one — and the claim that it gave the org review routing was exactly the kind of aspiration described as running that ORG_STANDARDS.md says it does not contain. What changes: - `.github/CODEOWNERS` says plainly that it covers this repo only, and its rules are narrowed to paths that exist here. It no longer lists /compiler/ and /wrangler.jsonc, which are in other repos and were never going to match anything from here. - `CODEOWNERS.example` at the root is the thing to copy per repo, following the pattern already set by dependabot.example.yml — the other file with no org-wide fallback. - ORG_STANDARDS.md moves CODEOWNERS out of the inherited list and into a new gap 14: no repo in the org has working review routing, and this repo cannot fix that centrally. Eight small PRs can. Also fixes the gap ordering, which read 11, 13, 12 after the SHA-pinning entry was inserted at the wrong anchor. Co-Authored-By: Claude Opus 5 (1M context) --- .github/CODEOWNERS | 73 ++++++++++++++++------------------------------ CODEOWNERS.example | 63 +++++++++++++++++++++++++++++++++++++++ ORG_STANDARDS.md | 51 ++++++++++++++++++++++---------- README.md | 4 +-- 4 files changed, 126 insertions(+), 65 deletions(-) create mode 100644 CODEOWNERS.example diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8ad615d..df39bf3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,63 +1,40 @@ -# Org-wide default owners. +# Code owners for THIS repository. # -# GitHub falls back to this file for any repo in `mzizi-dev` that does not -# ship its own `.github/CODEOWNERS`. Today that is EVERY repo except -# `mzizi-registry` — and that repo's own file is broken (see the note at the -# bottom), so in practice nothing in this org has working review routing -# until this file lands. +# SCOPE — read this before assuming it does more than it does. +# CODEOWNERS is NOT one of the files an organisation's `.github` repo can +# supply as an org-wide default. GitHub's list of inheritable community +# health files is CODE_OF_CONDUCT.md, CONTRIBUTING.md, discussion category +# forms, FUNDING.yml, issue and PR templates, SECURITY.md and SUPPORT.md — +# CODEOWNERS is absent from it, and the CODEOWNERS docs say the file is read +# from `.github/`, the root, or `docs/` "of the repository". So this file +# governs `mzizi-dev/.github` and nothing else. +# +# To get review routing in another repo, COPY `CODEOWNERS.example` from the +# root of this repo into that repo's own `.github/CODEOWNERS`. There is no +# way to do it centrally. # # The org has two members: @bryanfawcett (admin) and @michellellawson # (member). There are no teams, so owners are named as users. A team handle -# here would resolve to nobody and CODEOWNERS would silently do nothing — -# which is exactly the failure `mzizi-registry` is in. +# would resolve to nobody and CODEOWNERS would silently do nothing — which is +# exactly the state `mzizi-registry` is in (ORG_STANDARDS.md, gap 1). # -# GitHub applies the LAST matching pattern, so the catch-all must stay at the -# top. Add narrower rules BELOW it, never above. +# GitHub applies the LAST matching pattern, so the catch-all stays at the top. +# Add narrower rules BELOW it, never above. * @bryanfawcett # --------------------------------------------------------------------------- -# Narrower rules +# Narrower rules — paths that exist in THIS repo # --------------------------------------------------------------------------- -# These paths are named individually because they are the ones where an -# unreviewed change is expensive, not because they are the ones that change -# most. Paths are matched against the repo the PR is opened in, so a pattern -# that does not exist in a given repo simply never matches there. - -# The compiler and the primitive corpus in `mzizi-dev/mzizi`. Bundu -# Foundation IP, and the artefact every other repo is downstream of. -/compiler/ @bryanfawcett -/primitives/ @bryanfawcett -/examples/ @bryanfawcett -# The charter and the migration plan are the two documents that decide what -# this project is. They should not drift by accident. -/CHARTER.md @bryanfawcett -/MIGRATION.md @bryanfawcett - -# Anything that changes what CI runs, or what may merge. A PR that edits its -# own gate should be read by a human before it lands. +# The reusable workflows other repos will call. A change here changes CI in +# every repo that has adopted one, from a PR in a repo they do not watch. /.github/workflows/ @bryanfawcett -/.github/CODEOWNERS @bryanfawcett -/github-rulesets/ @bryanfawcett -# Deployment configuration for the two Workers (`mzizi-api-gateway`, -# `mzizi-console`). `wrangler.jsonc` carries the custom-domain routes; a -# wrong edit here takes a hostname down rather than failing a build. -/wrangler.jsonc @bryanfawcett +# Ruleset definitions. These decide what may merge across nine repos once +# applied. +/github-rulesets/ @bryanfawcett -# Secrets policy and the allowlist that decides what gitleaks ignores. -/.gitleaks.toml @bryanfawcett +# The org's factual record and the security policy every repo inherits. +/ORG_STANDARDS.md @bryanfawcett /SECURITY.md @bryanfawcett - -# --------------------------------------------------------------------------- -# Known defect this file does NOT fix -# --------------------------------------------------------------------------- -# `mzizi-dev/mzizi-registry` ships its own `.github/CODEOWNERS`, which takes -# precedence over this one for that repo. Every rule in it names -# `@nyuchi/core` — a team in the `nyuchi` org, not this one. Verified -# 2026-09-11: the `nyuchi` org's teams are docs, maintainers, marketing, -# mukoko, nyuchi-open-projects, platform and security. There is no `core` -# team, and a team from another org cannot own code here in any case. That -# file therefore assigns no reviewers at all. Fixing it is a PR against -# `mzizi-registry`, not this repo — see ORG_STANDARDS.md, "Known gaps". diff --git a/CODEOWNERS.example b/CODEOWNERS.example new file mode 100644 index 0000000..37779b2 --- /dev/null +++ b/CODEOWNERS.example @@ -0,0 +1,63 @@ +# Copy this to `.github/CODEOWNERS` in a consuming repo, then delete the +# sections that do not apply. +# +# WHY A TEMPLATE AND NOT A CENTRAL FILE +# CODEOWNERS is NOT inheritable from an organisation's `.github` repo. +# GitHub's list of community health files an org can supply as defaults is +# CODE_OF_CONDUCT.md, CONTRIBUTING.md, discussion category forms, FUNDING.yml, +# issue and PR templates, SECURITY.md and SUPPORT.md. CODEOWNERS is not on it, +# and the CODEOWNERS docs read the file from `.github/`, the root, or `docs/` +# "of the repository". Unlike SECURITY.md or the PR template, there is no +# central copy that covers the org — every repo needs its own file or it has +# no review routing at all. +# +# STATUS, 2026-09-11: `mzizi-registry` is the only repo in the org with a +# CODEOWNERS, and every rule in it names `@nyuchi/core` — a team in another +# org, which does not exist there either. It assigns nobody. So in practice +# no repo in `mzizi-dev` has working review routing today. +# +# RULES OF THE FORMAT +# - The LAST matching pattern wins. Keep the catch-all first and add +# narrower rules below it. +# - An owner must have write access to THIS repo. A user or team from +# another org silently matches nobody — no error, no warning, no +# reviewer. That is the failure mode above. +# - This org has no teams, so name users. + +# Default owner for everything. +* @bryanfawcett + +# --------------------------------------------------------------------------- +# Add below, not above. Uncomment what exists in your repo. +# --------------------------------------------------------------------------- + +# --- Anything that changes what CI runs, or what may merge --- +# A PR that edits its own gate should be read by a human before it lands. +# /.github/workflows/ @bryanfawcett +# /.github/CODEOWNERS @bryanfawcett + +# --- mzizi: the compiler and the corpus it must keep accepting --- +# /compiler/ @bryanfawcett +# /primitives/ @bryanfawcett +# /examples/ @bryanfawcett +# /CHARTER.md @bryanfawcett +# /MIGRATION.md @bryanfawcett + +# --- mzizi-api-gateway, mzizi-console: deployment configuration --- +# wrangler.jsonc carries the custom-domain routes. A wrong edit takes a +# hostname down rather than failing a build, so it fails late and loudly +# instead of early and cheaply. +# /wrangler.jsonc @bryanfawcett + +# --- mzizi-registry: what downstream consumers actually install --- +# This repo serves source into other people's production apps, so the files +# that decide what is served matter more than their size suggests. +# /registry.json @bryanfawcett +# /app/globals.css @bryanfawcett +# /lib/db/ @bryanfawcett + +# --- Secrets policy --- +# .gitleaks.toml is an allowlist: a change to it is a change to what the +# secret scan agrees to ignore. +# /.gitleaks.toml @bryanfawcett +# /SECURITY.md @bryanfawcett diff --git a/ORG_STANDARDS.md b/ORG_STANDARDS.md index 66f6d0f..96db1d7 100644 --- a/ORG_STANDARDS.md +++ b/ORG_STANDARDS.md @@ -332,14 +332,23 @@ fallback repo held a one-line README. `.github/dependabot.yml`. Its own copies take precedence over anything here, so two of them being broken (gaps 1 and 2) is not something this repo can fix. -What now applies org-wide by fallback: `.github/CODEOWNERS`, -`.github/PULL_REQUEST_TEMPLATE.md`, `.github/ISSUE_TEMPLATE/` (bug form, -feature form, and a `config.yml` routing security to a private advisory), -`SECURITY.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` (Contributor Covenant -2.1) and `SUPPORT.md`. - -**Dependabot does not have an org-wide fallback.** `.github/dependabot.yml` -here covers only this repo; `dependabot.example.yml` is a template to copy. +What now applies org-wide by fallback: `.github/PULL_REQUEST_TEMPLATE.md`, +`.github/ISSUE_TEMPLATE/` (bug form, feature form, and a `config.yml` routing +security to a private advisory), `SECURITY.md`, `CONTRIBUTING.md`, +`CODE_OF_CONDUCT.md` (Contributor Covenant 2.1) and `SUPPORT.md`. + +**Two things are NOT inheritable, and it is easy to assume otherwise.** + +- **CODEOWNERS.** GitHub's list of community health files an org `.github` + repo can supply as defaults is CODE_OF_CONDUCT.md, CONTRIBUTING.md, + discussion category forms, FUNDING.yml, issue and PR templates, SECURITY.md + and SUPPORT.md. CODEOWNERS is not on it, and the CODEOWNERS docs read the + file from `.github/`, the root, or `docs/` *of the repository*. So + `.github/CODEOWNERS` in this repo governs this repo and nothing else. + `CODEOWNERS.example` at the root is the template to copy into each repo. + This is gap 14. +- **Dependabot.** `.github/dependabot.yml` here covers only this repo; + `dependabot.example.yml` is the template. --- @@ -354,9 +363,10 @@ names `@nyuchi/core`. That is a team in the `nyuchi` org, not this one — and `nyuchi` has no `core` team either (its teams are docs, maintainers, marketing, mukoko, nyuchi-open-projects, platform, security). A team from another org cannot own code here in any case. Because a repo-local -`CODEOWNERS` takes precedence, the largest repo in the org is the one this -repo's `CODEOWNERS` does not reach. **Fix: a PR against `mzizi-registry` -replacing `@nyuchi/core` with `@bryanfawcett`.** +`CODEOWNERS` is the only kind there is — it is not inheritable from this repo +(gap 14) — the largest repo in the org has no working review routing at all. +**Fix: a PR against `mzizi-registry` replacing `@nyuchi/core` with +`@bryanfawcett`.** **2. `mzizi-registry`'s `SECURITY.md` points at the old org.** It sends reporters to `https://github.com/nyuchi/mzizi/security/advisories/new`. The @@ -429,6 +439,11 @@ If the fold is complete, the repo should be archived rather than left as a second place a roadmap might live — which `MIGRATION.md` §1 explicitly warns against: "do not leave a roadmap living apart from the code it plans." +**12. `allow_auto_merge` and `has_wiki` are inconsistent across repos.** +Cosmetic, but `has_wiki: true` on five repos leaves an unused, unwatched +surface open on a public org. `mzizi-roadmap` and `.github` also carry no +licence. + **13. GitHub can enforce SHA-pinning org-wide, and it is switched off.** `GET /orgs/mzizi-dev/actions/permissions` reports `sha_pinning_required: false` (with `enabled_repositories: all` and @@ -443,7 +458,13 @@ revisiting: every workflow in the org starts with a read-write `GITHUB_TOKEN` unless it narrows its own `permissions`, and `read` would be the safer default given all five workflows in this repo declare what they need. -**12. `allow_auto_merge` and `has_wiki` are inconsistent across repos.** -Cosmetic, but `has_wiki: true` on five repos leaves an unused, unwatched -surface open on a public org. `mzizi-roadmap` and `.github` also carry no -licence. +**14. No repo has working CODEOWNERS, and this repo cannot fix that +centrally.** CODEOWNERS is not an inheritable community health file (see +[Community-health files](#community-health-files) for the citation), so the +copy in this repo covers only this repo. `mzizi-registry` has the org's only +other CODEOWNERS and it assigns nobody (gap 1). Every other repo has none. +**Fix: copy `CODEOWNERS.example` into `.github/CODEOWNERS` in each repo — one +small PR per repo, eight of them.** Worth doing before +`require_code_owner_review` is ever turned on in a ruleset, because that +setting against a repo with no CODEOWNERS does nothing, and against one with +a broken CODEOWNERS blocks every PR. diff --git a/README.md b/README.md index b261eef..72fb627 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ in each repo today, and an explicit list of what does not exist yet. | Path | Applies to | |---|---| -| [`.github/CODEOWNERS`](./.github/CODEOWNERS) | Every repo without its own | +| [`.github/CODEOWNERS`](./.github/CODEOWNERS) | **This repo only.** CODEOWNERS is not inheritable — see [`CODEOWNERS.example`](./CODEOWNERS.example) | | [`.github/PULL_REQUEST_TEMPLATE.md`](./.github/PULL_REQUEST_TEMPLATE.md) | Every repo without its own | | [`.github/ISSUE_TEMPLATE/`](./.github/ISSUE_TEMPLATE) | Every repo without its own | | [`SECURITY.md`](./SECURITY.md) · [`CONTRIBUTING.md`](./CONTRIBUTING.md) · [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) · [`SUPPORT.md`](./SUPPORT.md) | Every repo without its own | | [`.github/workflows/reusable-*.yml`](./.github/workflows) | Any repo that calls them — opt-in, per repo | | [`github-rulesets/`](./github-rulesets) | Proposals. **Not applied.** | -| [`dependabot.example.yml`](./dependabot.example.yml) | A template to copy — Dependabot has no org-wide fallback | +| [`CODEOWNERS.example`](./CODEOWNERS.example) · [`dependabot.example.yml`](./dependabot.example.yml) | Templates to copy — neither CODEOWNERS nor Dependabot has an org-wide fallback | ## Reusable workflows From fd7086e773f772f076bebe289fd61ae923df9c5c Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Fri, 11 Sep 2026 22:03:33 +0800 Subject: [PATCH 2/3] fix: dependabot.yml's own comment still listed CODEOWNERS as inherited MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment explaining why Dependabot needs a per-repo file drew the contrast against "CODEOWNERS, issue templates and SECURITY.md" — which asserts the very thing the rest of this PR corrects. Caught by grepping every inheritance claim in the repo rather than only the ones in Markdown. It now names both exceptions, since they are the same instruction: copy the example file into each repo. Co-Authored-By: Claude Opus 5 (1M context) --- .github/dependabot.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 49f2302..9e24ab4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,9 +1,10 @@ # Dependabot for THIS repo only. # # A `dependabot.yml` in the org `.github` repo is NOT inherited by other -# repos — unlike CODEOWNERS, issue templates and SECURITY.md, Dependabot has -# no org-wide fallback. Each repo needs its own file. See -# `dependabot.example.yml` beside this one for a starting point to copy. +# repos. Issue and PR templates, SECURITY.md, CONTRIBUTING.md, SUPPORT.md and +# CODE_OF_CONDUCT.md do fall back to this repo; Dependabot and CODEOWNERS do +# not. Each repo needs its own copy of both. See `dependabot.example.yml` and +# `CODEOWNERS.example` at the root for starting points. # # The only thing here is a Cargo.toml-free repo of workflows, so # github-actions is the only ecosystem that applies. From 859183aae3b378ad557b0f9297ff163f8a7b1b78 Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Fri, 11 Sep 2026 22:04:40 +0800 Subject: [PATCH 3/3] docs: warn that an all-caps identifier trips the lowercase subject rule Found the hard way: this PR's original title was "fix: CODEOWNERS is not inheritable, so stop claiming it is", and the title lint rejected it. The rule is right and the title was wrong, but the failure reads as a style nit when it is really "your subject happens to start with a filename that is spelled in capitals". That will happen to anyone writing about CODEOWNERS, SECURITY.md, an API or a CI job, which in an org-standards repo is often. Documenting the reword is cheaper than answering it repeatedly, and much cheaper than the alternative someone will eventually propose: relaxing the pattern to allow leading capitals, which would stop it catching the actual mistake it exists for. Co-Authored-By: Claude Opus 5 (1M context) --- CONTRIBUTING.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 616a2f3..039f310 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,7 +53,12 @@ The PR title must parse as a Conventional Commit. Allowed types: Rules: -- Subject starts **lowercase**. +- Subject starts **lowercase**. This catches more than sloppiness: a subject + beginning with an all-caps identifier fails too, so + `fix: CODEOWNERS is not inheritable` is rejected and + `fix: stop claiming CODEOWNERS is inheritable` passes. Reword rather than + ask for the rule to be loosened — lowercasing the identifier itself would + be worse. - Subject is **imperative** — "add", not "adds" or "added". - No trailing period. - Scope is optional: `feat(compiler): ...` is fine, `feat: ...` is fine.