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/.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. 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/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. 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