From 4d0ec7c5f1bd656d394b44a51f11c350504208ea Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Fri, 11 Sep 2026 21:55:18 +0800 Subject: [PATCH 1/5] feat: populate the org defaults for mzizi-dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now this repo held a one-line README, so the eight other repos in the org inherited nothing: no CODEOWNERS, no issue or PR templates, no security policy, and no shared CI. Only `mzizi-registry` had any of those, and it has them locally, which means they cover one repo out of nine. Everything here was read off the GitHub API before it was written down. ORG_STANDARDS.md describes the CI that actually runs in each repo today and lists twelve things that do not exist, rather than describing the intended state as if it were the current one. Three reusable workflows, chosen for what this org actually is — Rust first, not npm first: - reusable-rust-ci.yml takes a `target` input and a separate `clippy-on-target` boolean. Two of the three Rust repos ship as WASM (mzizi-console in a browser, mzizi-api-gateway on workerd) and code can pass every native check and still fail to compile for the target that ships. The two repos need different answers — mzizi-console lints on the host and checks wasm32, mzizi-api-gateway must lint against wasm32 because the `worker` crate's API is cfg'd for it — so one knob would have forced them onto the same wrong answer. - reusable-gitleaks.yml runs the MIT binary directly. The gitleaks/gitleaks-action wrapper needs a paid licence for org repos. - reusable-pr-title-lint.yml enforces Conventional Commits on the PR title. Third-party actions are pinned by commit SHA, not tag; a tag can be moved to point at different code. The resolved versions are recorded in ORG_STANDARDS.md so a future reader can tell a pin from a guess. No repo calls these yet. Publishing and adopting in one change would move nine repos with no baseline to compare against; adoption is a small PR per repo, and this repo's own ci.yml dogfoods two of the three by local path so a change to a reusable is tested by the PR that makes it. github-rulesets/*.json are proposals and are NOT applied. Note that the org-wide one deliberately omits `required_linear_history`: that rule blocks merge commits, and a merge commit is the only merge this org permits. mzizi-registry's existing ruleset has exactly that combination today, which is gap 4. The docs reflect the merge-only convention throughout, per MIGRATION.md §1.1 — "Squash discards the per-commit reasoning this project depends on". One correction to the brief this was written against: the org has two members, @bryanfawcett (admin) and @michellellawson (member), not one. There are no teams, which is why CODEOWNERS names users. Co-Authored-By: Claude Opus 5 (1M context) --- .github/CODEOWNERS | 63 +++ .github/ISSUE_TEMPLATE/bug_report.yml | 87 ++++ .github/ISSUE_TEMPLATE/config.yml | 20 + .github/ISSUE_TEMPLATE/feature_request.yml | 60 +++ .github/PULL_REQUEST_TEMPLATE.md | 46 ++ .github/dependabot.yml | 25 + .github/workflows/ci.yml | 81 ++++ .github/workflows/pr-title-lint.yml | 22 + .github/workflows/reusable-gitleaks.yml | 87 ++++ .github/workflows/reusable-pr-title-lint.yml | 89 ++++ .github/workflows/reusable-rust-ci.yml | 147 ++++++ .gitignore | 1 + CODE_OF_CONDUCT.md | 95 ++++ CONTRIBUTING.md | 138 ++++++ ORG_STANDARDS.md | 428 ++++++++++++++++++ README.md | 53 ++- SECURITY.md | 110 +++++ SUPPORT.md | 45 ++ dependabot.example.yml | 62 +++ github-rulesets/org-wide-main-protection.json | 75 +++ github-rulesets/release-tag-protection.json | 39 ++ 21 files changed, 1772 insertions(+), 1 deletion(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pr-title-lint.yml create mode 100644 .github/workflows/reusable-gitleaks.yml create mode 100644 .github/workflows/reusable-pr-title-lint.yml create mode 100644 .github/workflows/reusable-rust-ci.yml create mode 100644 .gitignore create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 ORG_STANDARDS.md create mode 100644 SECURITY.md create mode 100644 SUPPORT.md create mode 100644 dependabot.example.yml create mode 100644 github-rulesets/org-wide-main-protection.json create mode 100644 github-rulesets/release-tag-protection.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..8ad615d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,63 @@ +# Org-wide default owners. +# +# 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. +# +# 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. +# +# GitHub applies the LAST matching pattern, so the catch-all must stay at the +# top. Add narrower rules BELOW it, never above. + +* @bryanfawcett + +# --------------------------------------------------------------------------- +# Narrower rules +# --------------------------------------------------------------------------- +# 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. +/.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 + +# Secrets policy and the allowlist that decides what gitleaks ignores. +/.gitleaks.toml @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/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..e68dc59 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,87 @@ +name: Bug report +description: Something behaves differently from what the docs, the charter or the code claim. +labels: ["bug"] +body: + - type: markdown + attributes: + value: > + Do not use this form for a security vulnerability. Report those + privately — see the "Security vulnerability" link on the previous + screen, or SECURITY.md. + + - type: dropdown + id: component + attributes: + label: Component + options: + - mzizi — the language, compiler or `mz` CLI + - mzizi-registry — the component registry / mzizi.dev + - mzizi-console — app.mzizi.dev (Astro + Dioxus WASM) + - mzizi-api-gateway — api.mzizi.dev (workers-rs) + - mzizi-site — mzizi.dev + - mzizi-docs — docs.mzizi.dev + - agent-tools — MCP server, fundi, CLI, skills + - org / CI / governance + - other or not sure + validations: + required: true + + - type: textarea + id: what + attributes: + label: What happened + description: > + Paste the exact error text, compiler diagnostic or response body. + Redact any key or token — this is a public issue. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What you expected instead + description: And, if you can, what made you expect it — a doc line, a charter claim, a type signature. + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to reproduce + placeholder: | + 1. cargo run --bin mz -- check examples/connectivity_bar.mz + 2. ... + validations: + required: true + + - type: input + id: version + attributes: + label: Version or commit + description: > + The git SHA you are on, or the released version. "main" on its own is + not enough — main moves. + placeholder: e.g. 5404607, or mz 0.1.0 + validations: + required: false + + - type: input + id: toolchain + attributes: + label: Rust toolchain and target + description: > + Output of `rustc -vV`, plus the target if it is not the host. A bug + that only appears on wasm32-unknown-unknown is a different bug from + one that appears natively, and the distinction is usually the whole + answer. + placeholder: e.g. rustc 1.90.0 (stable), target wasm32-unknown-unknown + validations: + required: false + + - type: checkboxes + id: hygiene + attributes: + label: Before you submit + options: + - label: This report contains no API key, token or credential. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..07a9215 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,20 @@ +blank_issues_enabled: true +contact_links: + - name: Security vulnerability + url: https://github.com/mzizi-dev/mzizi/security/advisories/new + about: > + Report privately. Never open a public issue for a vulnerability. This + link goes to the `mzizi` repo because private reporting is enabled + there; see SECURITY.md for which other repos accept it today. + - name: Documentation + url: https://docs.mzizi.dev + about: The Mzizi framework docs — language reference, runtime guides, agent surfaces. + - name: Component registry + url: https://mzizi.dev + about: Components, design tokens, and the registry API. + - name: Org standards, CI and governance + url: https://github.com/mzizi-dev/.github/blob/main/ORG_STANDARDS.md + about: What CI actually runs in each repo today, and the known gaps. + - name: Contributing + url: https://github.com/mzizi-dev/.github/blob/main/CONTRIBUTING.md + about: How to open a PR here, including why this org is merge-only. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..0fd2c99 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,60 @@ +name: Feature request +description: Propose a change in behaviour, a new capability, or a language change. +labels: ["enhancement"] +body: + - type: dropdown + id: component + attributes: + label: Component + options: + - mzizi — the language, compiler or `mz` CLI + - mzizi-registry — the component registry / mzizi.dev + - mzizi-console — app.mzizi.dev + - mzizi-api-gateway — api.mzizi.dev + - mzizi-site — mzizi.dev + - mzizi-docs — docs.mzizi.dev + - agent-tools — MCP server, fundi, CLI, skills + - org / CI / governance + - other or not sure + validations: + required: true + + - type: textarea + id: problem + attributes: + label: The problem + description: > + What is hard or impossible today? Describe the situation, not the + solution. If you already know the solution, it still helps to write + the problem down first — it is the part that outlives the proposal. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed change + validations: + required: true + + - type: textarea + id: thesis + attributes: + label: Fit with the charter + description: > + Mzizi's stated single sharp edge is "a Rust framework whose syntax, + type system, and compiler feedback loop are designed for machine + authorship" (mzizi/CHARTER.md §1). Proposals that make the language + better for a human typing, at the cost of an agent iterating against + the compiler, are the ones that need the most argument. Say which + side of that line this falls on. + validations: + required: false + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Including "do nothing" — say why that is worse. + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0f91920 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,46 @@ +## What this changes + + + +## Why + + + +## Commits + +- [ ] Each commit is a coherent step with a message that says *why*, not + just *what*. They are not going to be squashed away. +- [ ] No "fix typo" / "address review" commits left in the history — fold + them into the commit they belong to before requesting review. + +## Checks + +- [ ] CI is green on this PR. +- [ ] PR title follows Conventional Commits (`feat:`, `fix:`, `docs:`, …), + subject lowercase and imperative, no trailing period. +- [ ] `cargo fmt --check` and `cargo clippy -- -D warnings` pass locally for + any crate touched. + +## WASM + + + +- [ ] `cargo check --target wasm32-unknown-unknown --all-targets` passes. + +> `mzizi-console` ships as WASM in the browser and `mzizi-api-gateway` ships +> as WASM on workerd. Code can pass a native `cargo check` and still fail to +> compile for either. A native-only pass is not evidence. + +## Deployment + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..49f2302 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +# 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. +# +# The only thing here is a Cargo.toml-free repo of workflows, so +# github-actions is the only ecosystem that applies. + +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 5 + commit-message: + # Conventional Commits, to match the PR title lint. Dependabot + # capitalises the word after the prefix ("ci: Bump ..."), which the + # subject pattern rejects — that is why the lint workflow skips + # dependabot PRs rather than the pattern being loosened. + prefix: ci + labels: + - dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..240ab80 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,81 @@ +# CI for this repo. +# +# It also dogfoods what this repo publishes: the `secrets` job calls +# `reusable-gitleaks.yml` by LOCAL path (`uses: ./...`) rather than by +# `@main`. A local reference resolves against the commit under test, so the +# reusable workflow is exercised on the PR that changes it — a `@main` +# reference would run the already-merged copy and tell you nothing. + +name: CI + +on: + push: + branches: [main] + pull_request: + # Not just main. Stacked pull requests target the branch below them in + # the stack, and a filter of [main] silently gives every layer but the + # bottom one zero checks — a green PR with nothing run looks identical to + # a passing one. + branches: [main, "claude/**"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + actionlint: + name: actionlint + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + + # The prebuilt binary straight from the upstream release — no + # third-party action, no Go toolchain, no install script piped to a + # shell. + - name: Install and run actionlint + env: + ACTIONLINT_VERSION: 1.7.12 + run: | + set -euo pipefail + curl -fsSL -o actionlint.tar.gz \ + "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" + tar -xzf actionlint.tar.gz actionlint + chmod +x ./actionlint + ./actionlint -color + + rulesets: + name: rulesets parse + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + + # These files are applied with `gh api --input`. A JSON syntax error + # would only surface at the moment someone tries to change what can + # merge across nine repos, which is the worst possible time to find it. + - name: Every ruleset is valid JSON with the fields the API requires + run: | + set -euo pipefail + for f in github-rulesets/*.json; do + echo "checking $f" + python3 - "$f" <<'PY' + import json, sys + path = sys.argv[1] + with open(path) as fh: + data = json.load(fh) + for key in ("name", "target", "enforcement", "conditions", "rules"): + if key not in data: + sys.exit(f"{path}: missing required key {key!r}") + if data["target"] not in ("branch", "tag", "push"): + sys.exit(f"{path}: unexpected target {data['target']!r}") + PY + done + + secrets: + name: secret scan + uses: ./.github/workflows/reusable-gitleaks.yml diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml new file mode 100644 index 0000000..6bf1c4d --- /dev/null +++ b/.github/workflows/pr-title-lint.yml @@ -0,0 +1,22 @@ +# Conventional Commits on the PR title, for this repo. +# +# Calls this repo's own reusable by LOCAL path so a change to it is tested by +# the PR that makes the change. + +name: PR title + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +permissions: + pull-requests: read + statuses: write + +concurrency: + group: pr-title-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + pr-title: + uses: ./.github/workflows/reusable-pr-title-lint.yml diff --git a/.github/workflows/reusable-gitleaks.yml b/.github/workflows/reusable-gitleaks.yml new file mode 100644 index 0000000..76147ec --- /dev/null +++ b/.github/workflows/reusable-gitleaks.yml @@ -0,0 +1,87 @@ +# Reusable workflow: secret scanning with gitleaks. +# +# jobs: +# secrets: +# uses: mzizi-dev/.github/.github/workflows/reusable-gitleaks.yml@main +# +# --------------------------------------------------------------------------- +# Why the binary and not the action +# --------------------------------------------------------------------------- +# `gitleaks/gitleaks-action` requires a paid licence for organisation repos. +# The underlying gitleaks binary is MIT-licensed (verified 2026-09-11 against +# the gitleaks/gitleaks repo metadata), so this downloads and runs it +# directly. Same scan, no licence dependency, no third-party action in the +# trust path. Do not "simplify" this back to the action. +# +# --------------------------------------------------------------------------- +# Why full history by default +# --------------------------------------------------------------------------- +# A leaked key stays leaked once it is in a commit, whether or not that commit +# is the tip. `fetch-depth: 0` scans the whole history. This matters more than +# usual here: `mzizi-dev/mzizi` was produced by `git subtree split` out of +# another repository, so its commits arrived under this org's CI for the first +# time all at once — a tip-only scan would have said nothing about any of +# them. + +name: Reusable / Secret scan + +on: + workflow_call: + inputs: + gitleaks-version: + description: > + gitleaks release to download, without the leading `v`. The default + matches what every repo in this org runs today, so adopting this + workflow changes no behaviour. 8.30.1 was the latest release as of + 2026-09-11; bumping is a deliberate per-repo decision, because a + newer ruleset can surface findings in history that were previously + clean and turn adoption into a triage session. + type: string + default: "8.21.2" + fetch-depth: + description: 0 scans the full history. 1 scans only the checked-out tree. + type: number + default: 0 + config-path: + description: > + Path to a .gitleaks.toml. Empty lets gitleaks find its own default + (a `.gitleaks.toml` at the repo root is picked up automatically). + type: string + default: "" + timeout-minutes: + type: number + default: 10 + +permissions: + contents: read + +jobs: + gitleaks: + name: gitleaks + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout-minutes }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: ${{ inputs.fetch-depth }} + + - name: Install gitleaks + env: + VERSION: ${{ inputs.gitleaks-version }} + run: | + set -euo pipefail + curl -sSL --fail -o /tmp/gitleaks.tar.gz \ + "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" + sudo tar -xzf /tmp/gitleaks.tar.gz -C /usr/local/bin gitleaks + gitleaks version + + - name: Scan repository + env: + CONFIG: ${{ inputs.config-path }} + run: | + set -euo pipefail + args=(detect --source . --redact --no-banner --verbose) + if [ -n "$CONFIG" ]; then + args+=(--config "$CONFIG") + fi + gitleaks "${args[@]}" diff --git a/.github/workflows/reusable-pr-title-lint.yml b/.github/workflows/reusable-pr-title-lint.yml new file mode 100644 index 0000000..1b5965e --- /dev/null +++ b/.github/workflows/reusable-pr-title-lint.yml @@ -0,0 +1,89 @@ +# Reusable workflow: PR title lint (Conventional Commits). +# +# on: +# pull_request: +# types: [opened, edited, reopened, synchronize] +# jobs: +# pr-title: +# uses: mzizi-dev/.github/.github/workflows/reusable-pr-title-lint.yml@main +# +# --------------------------------------------------------------------------- +# What this does and does not claim, in a merge-only org +# --------------------------------------------------------------------------- +# In a squash-merge org, linting the PR title is really linting the commit +# subject that lands on main. mzizi-dev does not squash — squash and rebase +# merging are disabled on all nine repos, because "squash discards the +# per-commit reasoning this project depends on" (mzizi/MIGRATION.md §1.1). +# +# So be precise about what this gate is worth here. Every repo is set to +# merge_commit_title=MERGE_MESSAGE and merge_commit_message=PR_TITLE +# (verified across all nine, 2026-09-11), which means the merge commit reads: +# +# Merge pull request #12 from mzizi-dev/claude/org-defaults +# +# feat(ci): add the rust reusable workflow +# +# The PR title is the merge commit BODY, and it is the one line that +# summarises the whole branch in `git log --first-parent`. Your individual +# commits keep their own messages untouched — that is the point of merge-only, +# and this check does not inspect them. +# +# The allowed types track CONTRIBUTING.md. Change both together. + +name: Reusable / PR title lint + +on: + workflow_call: + inputs: + require-scope: + description: If true, a scope like `feat(compiler):` is required. + type: boolean + default: false + +permissions: + pull-requests: read + statuses: write + +jobs: + lint: + name: Conventional Commits + # Bot PR titles are skipped rather than accommodated. Dependabot titles + # its PRs "ci: Bump actions/checkout from 5 to 7" — capital B, which + # subjectPattern rejects, and it offers no setting to lowercase that word + # (commit-message.prefix controls only the prefix). Relaxing the pattern + # to permit uppercase would weaken it for humans too. Add other bots to + # this list rather than loosening the regex. + if: >- + ${{ github.event.pull_request.user.login != 'dependabot[bot]' + && github.event.pull_request.user.login != 'github-actions[bot]' }} + runs-on: ubuntu-latest + steps: + # Third-party action, pinned by commit SHA rather than tag: a tag can be + # moved to point at different code, a SHA cannot. This SHA is v6.1.1, + # which is also what the floating `v6` tag pointed at on 2026-09-11. + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + feat + fix + perf + refactor + docs + test + build + ci + chore + revert + style + requireScope: ${{ inputs.require-scope }} + subjectPattern: '^(?![A-Z])(?!.*\.$).+$' + subjectPatternError: >- + The subject "{subject}" is invalid for the "{type}" type: start it + lowercase, write it as an imperative ("add", not "adds"/"added"), + and do not end it with a period. + # Exempts titles literally starting with "wip" (case insensitive). + # This is NOT the same as GitHub's draft flag: a draft PR with an + # ordinary title still gets checked and can still fail. + wip: true diff --git a/.github/workflows/reusable-rust-ci.yml b/.github/workflows/reusable-rust-ci.yml new file mode 100644 index 0000000..3bd2ace --- /dev/null +++ b/.github/workflows/reusable-rust-ci.yml @@ -0,0 +1,147 @@ +# Reusable workflow: Rust CI — fmt, clippy, test, and an optional +# cross-target check. +# +# jobs: +# rust: +# uses: mzizi-dev/.github/.github/workflows/reusable-rust-ci.yml@main +# with: +# target: wasm32-unknown-unknown +# +# The check name GitHub reports is " / rust" — e.g. a caller +# job keyed `rust:` produces the context "rust / rust". Read the exact string +# off a completed run before naming it in a ruleset; a context that has never +# reported blocks every PR forever. +# +# --------------------------------------------------------------------------- +# Why `target` exists +# --------------------------------------------------------------------------- +# Two of this org's three Rust repos ship as WASM: `mzizi-console` runs in a +# browser, `mzizi-api-gateway` runs on workerd. Code can pass a native +# `cargo check` and fail to compile for either — conditional compilation, a +# dependency that is host-only, `std` surface that does not exist on wasm32. +# A native-only pass is not evidence about the artefact that actually ships. +# +# So `target` is not a convenience knob. Leave it empty only for a crate that +# genuinely never leaves the host (`mzizi`'s compiler). + +name: Reusable / Rust CI + +on: + workflow_call: + inputs: + working-directory: + description: Directory holding Cargo.toml. Defaults to the repo root. + type: string + default: "." + toolchain: + description: Rust toolchain — `stable`, `nightly`, or a pinned version like `1.90.0`. + type: string + default: stable + target: + description: > + Extra compilation target to check against, e.g. + wasm32-unknown-unknown. Empty means host-only. When set, the target + is installed and `cargo check --target --all-targets` runs + as its own step. + type: string + default: "" + clippy-on-target: + description: > + Run clippy against `target` instead of the host. Set this when the + crate's dependencies are cfg'd for that target and a host lint pass + would therefore be checking code the artefact never runs — which is + the case for workers-rs. Requires `target` to be set. + type: boolean + default: false + run-tests: + description: > + Run `cargo test` on the host. Stays useful for a WASM crate: the + pure logic — decoders, parsers, type shapes — is target-independent + and worth testing where a test harness actually exists. + type: boolean + default: true + test-args: + description: Extra arguments appended to `cargo test`. + type: string + default: "" + cache-workspaces: + description: > + Passed to Swatinem/rust-cache as `workspaces`. Set it to the same + value as `working-directory` when the crate is not at the repo root. + type: string + default: "." + timeout-minutes: + description: Job timeout. + type: number + default: 20 + +permissions: + contents: read + +jobs: + rust: + name: rust + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout-minutes }} + steps: + # Fail loudly on a contradictory call rather than silently doing the + # weaker thing. `clippy-on-target: true` with no target would otherwise + # lint the host and report green, which is the exact false pass this + # workflow exists to prevent. + - name: Validate inputs + if: ${{ inputs.clippy-on-target && inputs.target == '' }} + run: | + echo "::error::clippy-on-target is true but target is empty. Set target, or set clippy-on-target to false." + exit 1 + + - uses: actions/checkout@v5 + + # Third-party action, pinned by commit SHA: a tag can be moved to point + # at different code, a SHA cannot. This is the `stable` branch head of + # dtolnay/rust-toolchain as of 2026-09-11. + - name: Install Rust + uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + with: + toolchain: ${{ inputs.toolchain }} + components: clippy, rustfmt + targets: ${{ inputs.target }} + + # v2.9.2, dereferenced from the annotated tag to its commit. + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + workspaces: ${{ inputs.cache-workspaces }} + + - name: cargo fmt --check + working-directory: ${{ inputs.working-directory }} + run: cargo fmt --all -- --check + + - name: cargo clippy (host) + if: ${{ !inputs.clippy-on-target }} + working-directory: ${{ inputs.working-directory }} + run: cargo clippy --all-targets -- -D warnings + + - name: cargo clippy (${{ inputs.target }}) + if: ${{ inputs.clippy-on-target }} + working-directory: ${{ inputs.working-directory }} + env: + TARGET: ${{ inputs.target }} + run: cargo clippy --target "$TARGET" --all-targets -- -D warnings + + - name: cargo test + if: ${{ inputs.run-tests }} + working-directory: ${{ inputs.working-directory }} + env: + EXTRA: ${{ inputs.test-args }} + # Unquoted on purpose: EXTRA is a caller-supplied argument list and + # must word-split. It comes from the calling workflow file, not from + # PR-controlled input. + run: cargo test $EXTRA + + # The step that catches what a host build cannot. Runs last so a + # formatting or lint failure is reported before the slower compile. + - name: cargo check --target ${{ inputs.target }} + if: ${{ inputs.target != '' && !inputs.clippy-on-target }} + working-directory: ${{ inputs.working-directory }} + env: + TARGET: ${{ inputs.target }} + run: cargo check --target "$TARGET" --all-targets diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..09dac2e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,95 @@ + + + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at **conduct@nyuchi.com**. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..616a2f3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,138 @@ +# Contributing to mzizi-dev + +This is the org-wide fallback guide. A repo with its own `CONTRIBUTING.md` +overrides it — today only `mzizi-registry` has one. + +## The one thing that is different here + +**This org is merge-only.** Squash merging and rebase merging are disabled on +all nine repos. This is deliberate, not an oversight, and the reason is +written down in `mzizi-dev/mzizi`'s `MIGRATION.md` §1.1: + +| Allow merge commits | **yes** | The ecosystem convention is merge-only; history stays truthful | +| Allow squash merging | **no** | Squash discards the per-commit reasoning this project depends on | +| Allow rebase merging | **no** | Same | + +So: **your commits land on `main` exactly as you wrote them, and stay there.** +That changes how you should work. + +- Write each commit as a coherent step, with a message that explains *why*. + Nobody is going to squash your "wip" and "fix typo" commits away for you. +- Clean the branch up before requesting review — `git rebase -i` on your own + branch, not on `main`. +- Do not merge `main` into your branch to resolve a conflict. Rebase your + branch onto `main`, then let the PR merge produce the single merge commit. +- Merge with `gh pr merge --merge --delete-branch`. `--squash` and + `--rebase` will be rejected by the repo settings. + +The merge commit reads: + +``` +Merge pull request #12 from mzizi-dev/claude/org-defaults + +feat(ci): add the rust reusable workflow +``` + +Every repo is configured `merge_commit_title=MERGE_MESSAGE`, +`merge_commit_message=PR_TITLE`, so the PR title is the body — the one line +that summarises your whole branch in `git log --first-parent`. Write it as +carefully as a commit subject. + +## Branches + +Use a prefix that says who or what is doing the work: `feat/`, `fix/`, +`docs/`, or `claude/` for agent-authored branches. Branches are deleted on +merge (`delete_branch_on_merge` is true on all nine repos). + +## PR titles — Conventional Commits + +The PR title must parse as a Conventional Commit. Allowed types: + +`feat` · `fix` · `perf` · `refactor` · `docs` · `test` · `build` · `ci` · +`chore` · `revert` · `style` + +Rules: + +- Subject starts **lowercase**. +- Subject is **imperative** — "add", not "adds" or "added". +- No trailing period. +- Scope is optional: `feat(compiler): ...` is fine, `feat: ...` is fine. +- A title literally starting with `wip` is exempt. That is **not** the same + as marking the PR a draft — a draft PR with an ordinary title is still + checked and can still fail. + +This is enforced by +[`reusable-pr-title-lint.yml`](./.github/workflows/reusable-pr-title-lint.yml) +in repos that call it. See ORG_STANDARDS.md for which repos do today (the +honest answer right now: none). + +If you change the type list here, change it in that workflow too. They are +two copies of one decision. + +## Rust + +Most of this org is Rust. Before you push: + +```sh +cargo fmt --all -- --check +cargo clippy --all-targets -- -D warnings +cargo test +``` + +**If the crate ships as WASM, that is not enough.** `mzizi-console` runs in a +browser and `mzizi-api-gateway` runs on workerd; both compile to +`wasm32-unknown-unknown`. Code can pass every command above and still fail to +build for the target that actually ships: + +```sh +rustup target add wasm32-unknown-unknown +cargo check --target wasm32-unknown-unknown --all-targets +``` + +For `mzizi-api-gateway` specifically, run clippy against the target too — the +`worker` crate's API is `cfg`'d for wasm32, so a host lint pass checks code +the Worker never runs: + +```sh +cargo clippy --target wasm32-unknown-unknown --all-targets -- -D warnings +``` + +## CI triggers, if you are adding a workflow + +Filter pull requests on `[main, "claude/**"]`, not `[main]` alone: + +```yaml +on: + push: + branches: [main] + pull_request: + branches: [main, "claude/**"] +``` + +Stacked PRs target the branch below them in the stack. A filter of `[main]` +gives every layer but the bottom one **zero** checks — and a PR with nothing +run looks identical to a passing one in the UI. The existing `ci.yml` in +`mzizi`, `mzizi-console` and `mzizi-api-gateway` all carry this filter and a +comment explaining it. + +## Secrets + +Never commit a key, token or `.env`. `gitleaks` runs in CI on `mzizi`, +`mzizi-console` and `mzizi-api-gateway` and scans **full history**, so a +secret committed and then removed in a later commit still fails the build — +correctly. If that happens, rotate the credential first; rewriting history is +not a fix on its own. + +## Reporting a security issue + +Not through a PR or an issue. See [SECURITY.md](./SECURITY.md). + +## Where to ask + +[SUPPORT.md](./SUPPORT.md). + +## Licence + +Contributions are made under each repo's licence — Apache-2.0 for everything +that has one today. Mzizi framework, components and compiler logic are Bundu +Foundation IP (`mzizi/CHARTER.md`). diff --git a/ORG_STANDARDS.md b/ORG_STANDARDS.md new file mode 100644 index 0000000..7acd6dd --- /dev/null +++ b/ORG_STANDARDS.md @@ -0,0 +1,428 @@ +# Org CI and governance standards — `mzizi-dev` + +**Everything here was read off the GitHub API on 2026-09-11**, not inferred +from what ought to be true. Where something is a proposal rather than +something running today, it is in [Known gaps](#known-gaps) and says so. +Nothing on this page should be read as enforced unless it says it is. + +If you are adding CI to a repo in this org, the two sections worth reading +first are [The merge-only convention](#the-merge-only-convention) and +[Rust CI: the WASM trap](#rust-ci-the-wasm-trap). + +--- + +## The org + +Nine repos. Two members — `@bryanfawcett` (admin) and `@michellellawson` +(member). **No teams exist**, which is why `.github/CODEOWNERS` names users +rather than a `@mzizi-dev/...` handle. + +| Repo | Public | Stack | State | +|---|---|---|---| +| [`mzizi`](https://github.com/mzizi-dev/mzizi) | yes | Rust | The language, compiler and `mz` CLI. Bundu Foundation IP. Has content and CI | +| [`mzizi-registry`](https://github.com/mzizi-dev/mzizi-registry) | yes | Next.js (+ Rust crates) on Vercel | The component registry and mzizi.dev. The largest repo, and the only one with its own community-health files | +| [`mzizi-console`](https://github.com/mzizi-dev/mzizi-console) | yes | Astro + Rust/Dioxus WASM islands | app.mzizi.dev. Has content and CI | +| [`mzizi-api-gateway`](https://github.com/mzizi-dev/mzizi-api-gateway) | yes | Pure-Rust Cloudflare Worker (workers-rs) | api.mzizi.dev. Has content and CI | +| [`mzizi-site`](https://github.com/mzizi-dev/mzizi-site) | yes | — | **Completely empty** — no commits at all. Not "a README": the API returns "This repository is empty" | +| [`mzizi-docs`](https://github.com/mzizi-dev/mzizi-docs) | yes | Mintlify (planned) | README and LICENSE only. No `.github/` directory, no CI | +| [`mzizi-roadmap`](https://github.com/mzizi-dev/mzizi-roadmap) | yes | — | README only. `mzizi`'s own history shows the roadmap being folded into `mzizi/design/ROADMAP.md`, so this repo may be vestigial | +| [`agent-tools`](https://github.com/mzizi-dev/agent-tools) | **no** | TypeScript / pnpm | MCP server, `fundi` agent, CLI, skills. The most CI of any repo — ten workflows | +| [`.github`](https://github.com/mzizi-dev/.github) | yes | — | This repo | + +--- + +## The merge-only convention + +**Squash merging and rebase merging are disabled on all nine repos. +`allow_merge_commit` is true everywhere; `allow_squash_merge` and +`allow_rebase_merge` are false everywhere.** Verified on each repo +individually. + +This is a decision, not a default. `mzizi-dev/mzizi`'s `MIGRATION.md` §1.1: + +| Allow merge commits | **yes** | The ecosystem convention is merge-only; history stays truthful | +| Allow squash merging | **no** | Squash discards the per-commit reasoning this project depends on | +| Allow rebase merging | **no** | Same | + +It is visible in the history. `mzizi`, `mzizi-api-gateway` and `agent-tools` +all have two-parent `Merge pull request #N from mzizi-dev/...` commits at or +near the tip of `main`. + +Three consequences that are easy to get wrong: + +**1. Your commits are permanent, exactly as written.** Nothing folds them +together. Clean the branch up with an interactive rebase *on your own branch* +before requesting review, and write messages that explain why. + +**2. The PR title becomes the merge commit body, not its subject.** Every +repo is `merge_commit_title=MERGE_MESSAGE`, `merge_commit_message=PR_TITLE` +(verified on all nine). The result: + +``` +Merge pull request #12 from mzizi-dev/claude/org-defaults + +feat(ci): add the rust reusable workflow +``` + +So a PR-title lint here is not the squash-org version of the same check. It +is not guarding the commit subject on `main` — it is guarding the one line +that summarises the branch in `git log --first-parent`. Worth having, and +worth being precise about. + +**3. `required_linear_history` is incompatible with this org.** That ruleset +rule blocks merge commits, and a merge commit is the only merge this org +permits. The two together make every PR unmergeable. This is not theoretical +— see gap 4. + +Merge with: + +```sh +gh pr merge --merge --delete-branch +``` + +`--squash` and `--rebase` are rejected by the repo settings. + +--- + +## What CI actually runs, per repo + +Read from the workflow files in each repo's default branch. + +### `mzizi` — 2 workflows + +`ci.yml` triggers on push to `main` and pull requests to `main` **or** +`claude/**`, with `concurrency` cancelling superseded runs off `main`. + +| Job (check name) | What it runs | +|---|---| +| `compiler` | `cargo fmt -- --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test`, all in `compiler/` | +| `compiler` (cont.) | `mz check ../examples/connectivity_bar.mz`, then `mz check` over **every** file in `primitives/`, using the built binary rather than the test harness | +| `secret scan` | `gitleaks detect` at `fetch-depth: 0` — full history, because this repo arrived via `git subtree split` and every commit reached CI for the first time at once | + +`mzizi-lang-benchmark-dispatch.yml` is the second workflow. + +The `mz check` steps are the interesting part and are worth copying in +spirit: they assert the *shipped binary* still accepts the corpus, which is a +different claim from "the tests pass". + +### `mzizi-console` — 1 workflow + +| Job | What it runs | +|---|---| +| `rust` | fmt, clippy (host), `cargo test`, **and `cargo check --target wasm32-unknown-unknown --all-targets`** | +| `web` | pnpm 10.33.0, Node 22, `astro check`, `astro build`. The build is what proves the two toolchains compose — the island script references a bundle name derived from the crate name, so a rename that updates one and not the other fails here instead of serving a blank page | +| `secret scan` | gitleaks, full history | + +### `mzizi-api-gateway` — 1 workflow + +| Job | What it runs | +|---|---| +| `rust` | fmt; **clippy against `wasm32-unknown-unknown`**, not the host, because the `worker` crate's API is `cfg`'d for that target; `cargo check --target wasm32-unknown-unknown`; `cargo test` | +| `worker build` | `cargo install worker-build --version ^0.8`, `worker-build --release`, then `wrangler@4 deploy --dry-run`. The workflow's own comment records why the version pin is load-bearing: worker-build must track the `worker` dependency's minor line, and a pre-0.7 toolchain hard-codes a wasm-bindgen CLI version that cannot match what the crate compiled against | +| `secret scan` | gitleaks, full history | + +### `mzizi-registry` — 4 workflows + +`ci.yml` jobs, whose names are deliberately bare because the repo ruleset +requires them under those exact strings: **Security Audit**, **Registry +Snapshot**, **Rust**, **Lint**, **Type Check**, **Test**, **Build**. + +`lint.yml` jobs are the opposite convention — names carry a `lint / ` prefix +(`lint / actionlint`, `lint / JSON validity`, `lint / prettier`, +`lint / markdownlint`, `lint / yamllint`) because GitHub reports the bare +`name:` field to the Checks API and the UI grouping label is not part of it. +That asymmetry inside one repo is a live trap; the file itself documents it. + +Also present: `release.yml` (auto-release on a version bump) and +`reusable-ci-vite-plus.yml` — a reusable workflow that lives in the *registry* +repo rather than here. Its header explains why, and the reason is now stale: +see gap 7. + +Both `ci.yml` and `lint.yml` carry a `workflow_dispatch` trigger added after +an incident on 2026-08-26 where neither workflow produced any run for PR +#265 and there was no way to trigger them manually. + +Note the trigger difference: `mzizi-registry`'s workflows filter pull requests +on `[main]` only, while the three Rust repos use `[main, "claude/**"]`. + +### `agent-tools` — 10 workflows + +Private repo. `ci.yml` (`test`, `fundi / wrangler dry-run`), `lint.yml`, +`security.yml` (`pnpm audit --prod --audit-level high` plus a dependency +review, on push, PR and a weekly cron), `docs-check.yml`, `verify-fundi.yml` +(verifies the **live** A2A surface), `auto-assign.yml`, +`deprecate-legacy-npm.yml`, and three `publish-*.yml` workflows for the CLI, +the MCP server and the skills. + +`lint.yml` is the only workflow anywhere in this org that calls a reusable +workflow from another repo: +`uses: nyuchi/.github/.github/workflows/reusable-lint.yml@main`. + +### `mzizi-site`, `mzizi-docs`, `mzizi-roadmap` — 0 workflows + +No CI. `mzizi-site` has no commits at all. + +--- + +## Rust CI: the WASM trap + +The single most important thing to know before writing CI for this org. + +Two of the three Rust repos ship as WebAssembly: `mzizi-console` runs in a +browser, `mzizi-api-gateway` runs on workerd. **Code can pass every native +check and still fail to compile for the target that actually ships** — +conditional compilation, a host-only dependency, `std` surface that does not +exist on wasm32. A green native `cargo check` is not evidence about the +artefact. + +Both repos already handle this, and they handle it *differently*, correctly: + +- `mzizi-console` lints on the host and adds + `cargo check --target wasm32-unknown-unknown --all-targets`. +- `mzizi-api-gateway` runs **clippy itself** against wasm32, because the + `worker` crate's API is `cfg`'d for that target — a host lint pass there + would be linting code the Worker never runs. + +That is why +[`reusable-rust-ci.yml`](./.github/workflows/reusable-rust-ci.yml) takes both +a `target` input and a separate `clippy-on-target` boolean. One knob would +have forced the two repos onto the same wrong answer. + +--- + +## Reusable workflows published here + +A reusable workflow is a `.yml` under `.github/workflows/` with +`on: workflow_call`, called from another repo as +`uses: mzizi-dev/.github/.github/workflows/.yml@main`. + +As of 2026-09-11 this repo publishes three: + +| Workflow | Purpose | Notes | +|---|---|---| +| `reusable-rust-ci.yml` | fmt / clippy / test / cross-target check | `target` and `clippy-on-target` inputs cover the WASM trap above. `working-directory` covers `mzizi`, whose crate lives in `compiler/` | +| `reusable-gitleaks.yml` | Secret scan | Runs the MIT binary directly, **not** `gitleaks/gitleaks-action`, which requires a paid licence for org repos. Defaults to gitleaks 8.21.2 — the version every repo already runs, so adoption changes no behaviour | +| `reusable-pr-title-lint.yml` | Conventional Commits on the PR title | `amannn/action-semantic-pull-request` pinned by commit SHA | + +**Third-party actions are pinned by commit SHA, not tag.** A tag can be moved +to point at different code; a SHA cannot. `actions/*` are first-party GitHub +and stay on major tags. The pinned SHAs and what they resolved to on +2026-09-11: + +| Action | SHA | Resolves to | +|---|---|---| +| `dtolnay/rust-toolchain` | `6bed076…` | head of the `stable` branch — this action publishes no semver tags, so a branch head is the only thing to pin | +| `Swatinem/rust-cache` | `6323deb…` | v2.9.2 (dereferenced from the annotated tag) | +| `amannn/action-semantic-pull-request` | `48f2562…` | v6.1.1, which is also where the floating `v6` tag pointed | + +**No repo calls any of these yet.** They are published first so that adopting +one is a small reviewable PR against a single repo, with a real CI run to +compare against, rather than nine repos changing at once. Adoption order that +makes sense: `mzizi-api-gateway` (smallest Rust surface) → `mzizi-console` → +`mzizi`. + +### Why not just use `nyuchi/.github`? + +`nyuchi/.github` is public and has twenty reusable workflows, including +`reusable-ci-rust-monorepo.yml`, and `agent-tools` already calls its +`reusable-lint.yml` across orgs. So the question is fair, and the answer is +specific rather than territorial — **its Rust workflow has no cross-target +support at all.** Read on 2026-09-11, it has exactly one input (`toolchain`). +There is no `target`, no `wasm32` anywhere in the file, and no +`working-directory`. It runs `cargo clippy --workspace`, +`cargo nextest run --workspace`, `cargo build --workspace --release`, +`cargo doc` and a conditional `cargo deny`, all against the host. + +For this org that means: + +- **It cannot check either WASM artefact.** The one property that matters + most for `mzizi-console` and `mzizi-api-gateway` is the one it does not + test. +- **It assumes a workspace at the repo root.** `mzizi`'s crate is in + `compiler/`; with no `working-directory` input, `--workspace` from the root + finds nothing. +- **It requires `cargo nextest`**, which none of these repos use today. +- It sets `RUSTFLAGS: -D warnings` globally, so a warning anywhere fails + every job rather than the lint job. + +It is a good workflow for a root Cargo workspace built for the host. That is +not the shape of any Rust repo here. `reusable-lint.yml` is a genuinely +useful cross-org call and should stay; the Rust one is not a drop-in, and +forking is the honest option rather than the lazy one. + +--- + +## Branch protection and rulesets + +**Nothing is enforced today.** `GET /orgs/mzizi-dev/rulesets` returns `[]`. +No repo has classic branch protection — every one returns "Branch not +protected". There is exactly one ruleset in the entire org, and it is +repo-level: + +**`mzizi-registry` → ruleset "Default"** (id 14801708, active, no bypass +actors): + +| Rule | Parameters | +|---|---| +| `required_linear_history` | — | +| `pull_request` | 0 approvals required, review-thread resolution off, `allowed_merge_methods: ["merge", "squash", "rebase"]` | +| `required_status_checks` | `Lint`, `Type Check`, `Build`, `Security Audit`, `Test` | + +That ruleset is the org's only worked example of required status checks, and +it is also broken — see gap 4. + +Proposed replacements live in [`github-rulesets/`](./github-rulesets) as +versioned JSON: + +- **`org-wide-main-protection.json`** — `deletion`, `non_fast_forward`, + `required_signatures`, and a `pull_request` rule with + `allowed_merge_methods: ["merge"]`. Deliberately **no** + `required_linear_history`, for the reason above. +- **`release-tag-protection.json`** — makes `v*` tags immutable. + +**Neither is applied.** Applying an org ruleset changes what can merge across +nine repos at once; that is a human decision, and the apply command is in +each file's `_comment`. Two things to settle before applying: + +- `required_signatures` rejects any unsigned commit, including from a bot + that is not configured to sign. Confirm every author signs, or drop that + rule in the first pass. +- `required_status_checks` is absent on purpose. A check context that has + never reported makes every PR permanently unmergeable. Add contexts per + repo only after reading the exact names off a completed run — the names CI + emits today are in [What CI actually runs](#what-ci-actually-runs-per-repo). + +--- + +## Repository settings, as they actually are + +Uniform across all nine unless noted. + +| Setting | Value | Note | +|---|---|---| +| `allow_merge_commit` | true | The only permitted method | +| `allow_squash_merge` / `allow_rebase_merge` | false | MIGRATION.md §1.1 | +| `merge_commit_title` | `MERGE_MESSAGE` | "Merge pull request #N from …" | +| `merge_commit_message` | `PR_TITLE` | The PR title becomes the body | +| `delete_branch_on_merge` | **true** on all nine | Already correct — no cleanup needed | +| `allow_auto_merge` | true on `mzizi-registry`, `mzizi-api-gateway`, `agent-tools`; **false** on the other six | Inconsistent | +| `has_wiki` | false on `mzizi`, `mzizi-registry`, `mzizi-api-gateway`; **true** on the other six | Unused surface, on by default | +| Licence | Apache-2.0 on six; **none** on `mzizi-roadmap` and `.github` | | +| Secret scanning | **enabled on 2 of 8 public repos** — `mzizi-registry`, `mzizi-api-gateway` | | +| Secret scanning push protection | Same two | | +| Dependabot security updates | **disabled on all nine** | | +| Private vulnerability reporting | **enabled on 2 of 8** — `mzizi`, `mzizi-registry`. Not available on `agent-tools` (private repo) | Determines where a security report can actually be filed | + +--- + +## Community-health files + +Before this repo was populated, **`mzizi-registry` was the only repo in the +org with any of them**, and every other repo inherited nothing, because this +fallback repo held a one-line README. + +`mzizi-registry` has its own `.github/CODEOWNERS`, `SECURITY.md`, +`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `.github/ISSUE_TEMPLATE/` and +`.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. + +--- + +## Known gaps + +Verified 2026-09-11. These are documented, not fixed — several are one-line +API calls someone with admin rights should make deliberately, and the rest +are PRs against repos other than this one. + +**1. `mzizi-registry`'s own CODEOWNERS assigns nobody.** Every rule in it +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`.** + +**2. `mzizi-registry`'s `SECURITY.md` points at the old org.** It sends +reporters to `https://github.com/nyuchi/mzizi/security/advisories/new`. The +repo was renamed to `mzizi-dev/mzizi-registry`; GitHub's redirect means the +link happens to resolve, but it names an org that no longer owns the code, +and the version table (4.0.x / 4.1.x) pre-dates the move. + +**3. Private vulnerability reporting is off on six of eight public repos.** +Enabled only on `mzizi` and `mzizi-registry`. A reporter following +`SECURITY.md` to `mzizi-console` or `mzizi-api-gateway` finds no private +channel and is pushed toward a public issue — the exact thing the policy +tells them not to do. This repo's `SECURITY.md` works around it by routing +everything to `mzizi`, which is a workaround, not a fix. **Fix: +`gh api -X PUT repos/mzizi-dev//private-vulnerability-reporting` per +repo.** + +**4. `mzizi-registry`'s ruleset contradicts the merge-only convention.** Its +"Default" ruleset requires `required_linear_history`, which blocks merge +commits — while the repo's only enabled merge method *is* the merge commit. +The next PR merged there with the merge button should be rejected by the +ruleset. Stated as a prediction rather than an observation, honestly: the +last merges on that repo (PRs #317, #318, 2026-09-08) produced single-parent +commits in squash format, so they pre-date the merge-only settings and no +merge commit has been attempted against the rule yet. **Fix: drop +`required_linear_history` from that ruleset and set `allowed_merge_methods` +to `["merge"]`.** + +**5. Secret scanning and push protection are off on six of eight public +repos**, including `mzizi` itself. Push protection stops a secret reaching +the remote; the `gitleaks` CI job only catches what is already committed. +They are complements. `mzizi` and `mzizi-console` currently have the CI half +alone; `mzizi-site`, `mzizi-docs`, `mzizi-roadmap` and `.github` have +neither. Dependabot security updates are off everywhere. + +**6. No repo calls the reusable workflows in this repo.** Deliberate for this +pass — publishing and adopting in one change would mean nine repos moving +with no baseline to compare against. Until adoption, each repo's CI stays +duplicated, and a fix to (say) the gitleaks install has to be made three +times. Adoption is tracked as a follow-up per repo. + +**7. A reusable workflow lives in `mzizi-registry` on a stale premise.** +`mzizi-registry/.github/workflows/reusable-ci-vite-plus.yml` explains that it +lives there rather than in the org `.github` repo because that repo's "name +begins with a dot, which makes it unattachable to an agent session and so +unmaintainable by the tooling that maintains everything else here". This PR +is a counter-example — the repo is editable. Worth revisiting whether that +workflow should move here, though the migration order it documents (add the +check contexts to rulesets in the right sequence or every PR blocks) is real +and should be followed if it does. + +**8. No `required_status_checks` in the proposed org ruleset.** Deliberate — +naming a context that has never reported blocks every PR. Add per repo after +CI has run once and the exact names are readable off a completed run. + +**9. Trigger filters are inconsistent.** The three Rust repos filter pull +requests on `[main, "claude/**"]`; `mzizi-registry` filters on `[main]` +alone. Stacked PRs target the branch below them in the stack, so on +`mzizi-registry` every layer above the bottom one currently gets **zero** +checks — and a PR with nothing run is visually indistinguishable from a +passing one. + +**10. Three repos have no CI because they have nearly no content.** +`mzizi-site` is completely empty (no commits); `mzizi-docs` and +`mzizi-roadmap` hold a README. Nothing to fix until there is something to +check — noted so the absence is not mistaken for an oversight. + +**11. `mzizi-roadmap` may be vestigial.** `mzizi`'s history contains +`docs: fold mzizi-dev/mzizi-roadmap into design/ROADMAP.md` (PR #3, merged). +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 six repos leaves an unused, unwatched +surface open on a public org. diff --git a/README.md b/README.md index a46ae92..b261eef 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ -# .github \ No newline at end of file +# mzizi-dev/.github + +Org-wide defaults for the [`mzizi-dev`](https://github.com/mzizi-dev) GitHub +organisation. GitHub falls back to what lives here for any repo in the org +that does not ship its own copy, and this is where the org's reusable +workflows live. + +**Start here: [ORG_STANDARDS.md](./ORG_STANDARDS.md)** — what CI actually runs +in each repo today, and an explicit list of what does not exist yet. + +## What is in here + +| Path | Applies to | +|---|---| +| [`.github/CODEOWNERS`](./.github/CODEOWNERS) | Every repo without its own | +| [`.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 | + +## Reusable workflows + +```yaml +jobs: + rust: + uses: mzizi-dev/.github/.github/workflows/reusable-rust-ci.yml@main + with: + target: wasm32-unknown-unknown + secrets: + uses: mzizi-dev/.github/.github/workflows/reusable-gitleaks.yml@main +``` + +| Workflow | What it does | +|---|---| +| [`reusable-rust-ci.yml`](./.github/workflows/reusable-rust-ci.yml) | fmt, clippy, test, and an optional check against a second target — `wasm32-unknown-unknown` is the one that matters for `mzizi-console` and `mzizi-api-gateway` | +| [`reusable-gitleaks.yml`](./.github/workflows/reusable-gitleaks.yml) | Secret scan, running the MIT-licensed binary directly rather than the paid-licence wrapper action | +| [`reusable-pr-title-lint.yml`](./.github/workflows/reusable-pr-title-lint.yml) | Conventional Commits on the PR title | + +No repo calls these yet — they are published here first so adoption is a +reviewable PR per repo rather than a big-bang change. See ORG_STANDARDS.md. + +## This org is merge-only + +Squash and rebase merging are disabled on all nine repos, deliberately: +`mzizi/MIGRATION.md` §1.1 — *"Squash discards the per-commit reasoning this +project depends on."* + +Merge with `gh pr merge --merge --delete-branch`. Write your commits for +the person reading them in a year; they are not going to be squashed away. +[CONTRIBUTING.md](./CONTRIBUTING.md) has the rest. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9b7da98 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,110 @@ +# Security policy + +This file is the org-wide fallback for `mzizi-dev`. GitHub shows it for any +repo in the org that does not ship its own `SECURITY.md`. Today that is every +repo except `mzizi-registry`, which has its own — and that one currently +points at a stale URL (see "Known problems" below). + +## Reporting a vulnerability + +**Do not open a public issue, pull request or discussion.** + +Report privately through GitHub Security Advisories, on the repo the finding +affects: + +| Repo | Private reporting | Advisory link | +|---|---|---| +| `mzizi` | **enabled** | | +| `mzizi-registry` | **enabled** | | +| `mzizi-console` | not enabled | — | +| `mzizi-api-gateway` | not enabled | — | +| `mzizi-site`, `mzizi-docs`, `mzizi-roadmap`, `.github` | not enabled | — | +| `agent-tools` | n/a — private repo | — | + +Verified 2026-09-11 against the GitHub API. If the repo you need is in the +"not enabled" rows, **report against `mzizi-dev/mzizi`** and say in the +report which component it actually concerns; it reaches the same maintainers. +Enabling private reporting on the remaining repos is a tracked gap — see +[ORG_STANDARDS.md](./ORG_STANDARDS.md#known-gaps). + +If GitHub advisories are unavailable to you, email **security@nyuchi.com** +with the same information. Nyuchi Africa operates the Mzizi surfaces +commercially and that mailbox is the one already in use for this ecosystem; +it is not a separate team. + +Expect an acknowledgement within **three working days**. A partial report +sent early is more useful than a complete one sent late. + +## What to include + +- The affected repo, and the surface — a URL, an endpoint, a crate, an MCP + tool, a registry item. +- A reproduction. For `mzizi-api-gateway`, the request. For `mzizi`, the + `.mz` source or the compiler invocation. For `mzizi-registry`, the item or + API call. +- The version or commit SHA you were on. +- The impact you believe it has, and who is exposed. +- A proposed fix if you have one — patches are welcome through the advisory + UI. + +## Scope + +In scope: + +| Repo | Surface | +|---|---| +| `mzizi` | The compiler and the `mz` CLI — anything where checking or compiling untrusted `.mz` source can execute code, escape the working directory, or exhaust the host | +| `mzizi-api-gateway` | api.mzizi.dev — the Worker: routing, auth token handling, request and response validation | +| `mzizi-registry` | mzizi.dev — the registry API and the component source it serves | +| `mzizi-console` | app.mzizi.dev — the console and its WASM islands | +| `mzizi-site`, `mzizi-docs` | The public site and documentation | +| This repo | The reusable workflows, and the ruleset definitions under `github-rulesets/` | + +Out of scope: volumetric denial of service, findings against Cloudflare or +Vercel themselves, missing headers with no demonstrated impact, and raw +scanner output with no exploit path. + +## What we treat as most serious + +**Supply chain, above everything else.** `mzizi-registry` serves source code +and AI-facing instructions into downstream production apps across the Bundu +ecosystem, and `mzizi` compiles source that agents author automatically. A +finding that lets an attacker change what a downstream consumer installs, or +what a compiler emits, is critical regardless of how hard it is to reach — +because the blast radius is every consumer, not one endpoint. + +Specifically: + +1. **Anything that changes what the registry serves** for an existing + component, or that causes a component to install something other than what + its source says. +2. **Anything that lets untrusted `.mz` input influence the host** during a + `mz check` or a compile — the whole design assumes an agent runs that in a + loop, unattended, thousands of times. +3. **Credential exposure in the Worker** — `mzizi-api-gateway` holds the + tokens for api.mzizi.dev. + +## Disclosure + +We confirm the report, agree a timeline with you, and credit you in the +advisory unless you ask otherwise. Please give a reasonable window to ship a +fix before publishing. + +## Known problems with this policy + +Stated here rather than quietly fixed elsewhere, because they affect where a +report actually lands: + +- **`mzizi-registry`'s own `SECURITY.md` points at + `https://github.com/nyuchi/mzizi/security/advisories/new`.** That repo was + renamed to `mzizi-dev/mzizi-registry`; GitHub redirects the path, so the + link happens to work today, but it names an org that no longer owns the + code. It also cites version lines (4.0.x / 4.1.x) that pre-date the move. + Fixing it is a PR against that repo. +- **Secret scanning and push protection are enabled on only two of the eight + public repos** — `mzizi-registry` and `mzizi-api-gateway`. The other six, + including `mzizi` itself, have both switched off. Push protection is what + stops a secret ever reaching the remote; the `gitleaks` job in CI only + catches what is already committed. They are complements, not alternatives, + and `mzizi` and `mzizi-console` currently rely on the CI half alone. + Dependabot security updates are off on every repo in the org. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..3905ae2 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,45 @@ +# Support + +## Documentation + +| Surface | Where | Status | +|---|---|---| +| Framework docs | | `mzizi-docs` holds only a README and LICENSE today — the site is being built | +| Component registry | | `mzizi-registry` is the live registry and portal | +| Console | | `mzizi-console` | +| API | | `mzizi-api-gateway` | +| Language, charter, RFCs | [`mzizi-dev/mzizi`](https://github.com/mzizi-dev/mzizi) | `CHARTER.md` is the authoritative statement of what this project is for | + +## Where to raise what + +| What | Where | +|---|---| +| A compiler bug, a language question, an `mz` CLI problem | An issue on [`mzizi`](https://github.com/mzizi-dev/mzizi/issues) | +| A component, token or registry-API problem | An issue on [`mzizi-registry`](https://github.com/mzizi-dev/mzizi-registry/issues) | +| Something wrong at app.mzizi.dev | An issue on [`mzizi-console`](https://github.com/mzizi-dev/mzizi-console/issues) | +| Something wrong at api.mzizi.dev | An issue on [`mzizi-api-gateway`](https://github.com/mzizi-dev/mzizi-api-gateway/issues) | +| A documentation error | An issue on [`mzizi-docs`](https://github.com/mzizi-dev/mzizi-docs/issues) | +| Roadmap and sequencing | [`mzizi-roadmap`](https://github.com/mzizi-dev/mzizi-roadmap/issues) | +| CI, governance, org standards, this file | An issue on [`mzizi-dev/.github`](https://github.com/mzizi-dev/.github/issues) | +| A security vulnerability | **Not an issue.** See [SECURITY.md](./SECURITY.md) | + +`agent-tools` is private. If your question is about the MCP server, `fundi`, +the CLI or the skills and you cannot open an issue there, raise it on +`mzizi-dev/.github` and it will be routed. + +## What helps + +- **For a compiler report**: the `.mz` source, the exact `mz` invocation, the + full diagnostic, and the commit SHA. Not "main" — main moves. +- **For a WASM build failure**: say whether it reproduces on the host. A + failure that only appears under `--target wasm32-unknown-unknown` is + usually a different problem from one that appears natively, and that + distinction is often the whole answer. +- **For a registry problem**: the component name and the install command you + ran. + +## Response times + +Mzizi is a Bundu Foundation research project maintained by a small team. The +org has two members. Issues are read; a same-day reply is not promised. +Security reports are acknowledged within three working days. diff --git a/dependabot.example.yml b/dependabot.example.yml new file mode 100644 index 0000000..421be7e --- /dev/null +++ b/dependabot.example.yml @@ -0,0 +1,62 @@ +# Copy this to `.github/dependabot.yml` in a consuming repo and delete the +# ecosystems that do not apply. Dependabot has no org-wide fallback — a file +# in `mzizi-dev/.github` covers only that repo. +# +# Status note, 2026-09-11: `mzizi-registry` is the ONLY repo in this org with +# a `.github/dependabot.yml` today. Dependabot *security* updates are +# disabled org-wide, so for the other eight repos neither the version-bump +# PRs configured here nor the automatic security fixes are running. + +version: 2 +updates: + # ---- Every repo with workflows ---- + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 5 + commit-message: + prefix: ci + labels: + - dependencies + + # ---- Rust: mzizi (directory /compiler), mzizi-console, mzizi-api-gateway ---- + # `directory` must point at the directory holding Cargo.toml. In `mzizi` + # that is `/compiler`, not `/`. + - package-ecosystem: cargo + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 5 + commit-message: + prefix: build + labels: + - dependencies + groups: + # One PR for the routine patch churn, individual PRs for majors. A + # wasm32 target makes a Rust bump more likely than usual to break the + # build rather than just the tests, so majors are worth reading alone. + cargo-minor-patch: + update-types: + - minor + - patch + + # ---- npm/pnpm: mzizi-registry, mzizi-console (the Astro half), agent-tools ---- + - package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 + commit-message: + prefix: build + labels: + - dependencies + groups: + types: + patterns: + - "@types/*" + dev-minor-patch: + dependency-type: development + update-types: + - minor + - patch diff --git a/github-rulesets/org-wide-main-protection.json b/github-rulesets/org-wide-main-protection.json new file mode 100644 index 0000000..f0e4d90 --- /dev/null +++ b/github-rulesets/org-wide-main-protection.json @@ -0,0 +1,75 @@ +{ + "_comment": [ + "Org-wide protection for every repo's default branch in mzizi-dev.", + "", + "NOT APPLIED. As of 2026-09-11 `GET /orgs/mzizi-dev/rulesets` returns an", + "empty array and no repo has classic branch protection. This file is a", + "reviewable proposal; applying it changes what can merge across nine", + "repos and is a human decision. Apply with:", + "", + " gh api -X POST orgs/mzizi-dev/rulesets --input org-wide-main-protection.json", + "", + "Four choices worth understanding before applying:", + "", + "1. allowed_merge_methods is [\"merge\"], NOT [\"squash\"]. This org is", + " merge-only by deliberate convention: mzizi/MIGRATION.md 1.1 says", + " \"Squash discards the per-commit reasoning this project depends on\".", + " All nine repos already have allow_squash_merge and", + " allow_rebase_merge set to false; this makes the same rule a gate", + " rather than only a hidden button.", + "", + "2. There is deliberately NO required_linear_history rule. That rule", + " blocks merge commits, and a merge commit is the ONLY merge this org", + " permits — the two together would make every PR unmergeable. This is", + " not hypothetical: mzizi-registry's existing repo-level ruleset", + " (\"Default\", id 14801708) has exactly that combination today and", + " should be corrected. See ORG_STANDARDS.md, Known gaps.", + "", + "3. required_approving_review_count is 0. The org has two members but", + " effectively one active reviewer; a count of 1 would make every PR", + " unmergeable by its own author, which is a lockout, not a safeguard.", + " The pull_request rule still forces changes through a PR. Raise this", + " to 1 once a second maintainer is reviewing regularly.", + "", + "4. required_signatures is included. It is the rule most likely to", + " surprise: an unsigned commit is rejected outright, including one", + " made by a tool that is not configured to sign. Confirm every author", + " and every bot in the org signs before applying, or drop this rule", + " in the first pass and add it separately.", + "", + "required_status_checks is deliberately absent. Naming a check context", + "that has never reported makes every PR permanently unmergeable. Add", + "contexts per repo only after reading the exact job names off a", + "completed run — see ORG_STANDARDS.md for the names CI emits today." + ], + "name": "org-wide-main-protection", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] }, + "repository_name": { "include": ["~ALL"], "exclude": [] } + }, + "bypass_actors": [ + { + "actor_id": 1, + "actor_type": "OrganizationAdmin", + "bypass_mode": "always" + } + ], + "rules": [ + { "type": "deletion" }, + { "type": "non_fast_forward" }, + { "type": "required_signatures" }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": true, + "allowed_merge_methods": ["merge"] + } + } + ] +} diff --git a/github-rulesets/release-tag-protection.json b/github-rulesets/release-tag-protection.json new file mode 100644 index 0000000..11a156d --- /dev/null +++ b/github-rulesets/release-tag-protection.json @@ -0,0 +1,39 @@ +{ + "_comment": [ + "Release tags are immutable once pushed. Without this, a v1.2.3 tag can", + "be moved to point at different code after the fact, silently", + "invalidating anything that pinned it.", + "", + "NOT APPLIED. Reviewable proposal only. Apply with:", + "", + " gh api -X POST orgs/mzizi-dev/rulesets --input release-tag-protection.json", + "", + "This matters more here than the name suggests. mzizi-dev publishes into", + "other people's builds — `agent-tools` publishes the mzizi CLI, MCP", + "server and skills to npm, and `mzizi-registry` serves component source", + "into downstream production apps. A moved tag in this org is a supply", + "chain problem, not a housekeeping one.", + "", + "The `update` rule is what makes a tag immutable; `non_fast_forward` and", + "`deletion` stop the two ways around it." + ], + "name": "release-tag-protection", + "target": "tag", + "enforcement": "active", + "conditions": { + "ref_name": { "include": ["refs/tags/v*"], "exclude": [] }, + "repository_name": { "include": ["~ALL"], "exclude": [] } + }, + "bypass_actors": [ + { + "actor_id": 1, + "actor_type": "OrganizationAdmin", + "bypass_mode": "always" + } + ], + "rules": [ + { "type": "deletion" }, + { "type": "non_fast_forward" }, + { "type": "update" } + ] +} From db12566c37e2ad6d7fd8be24183dec1aa1031460 Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Fri, 11 Sep 2026 21:56:27 +0800 Subject: [PATCH 2/5] docs: say what the benchmark dispatch workflow actually does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "is the second workflow" told a reader nothing. It dispatches to a held-out runner named by a repo variable, and it is fork-guarded — both worth knowing, because the held-out task set is withheld on purpose (MIGRATION.md §5: "withheld so the benchmark measures the language rather than memorisation") and a reader who does not know that might reasonably try to make the dispatch target public. Co-Authored-By: Claude Opus 5 (1M context) --- ORG_STANDARDS.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ORG_STANDARDS.md b/ORG_STANDARDS.md index 7acd6dd..d6f8aa1 100644 --- a/ORG_STANDARDS.md +++ b/ORG_STANDARDS.md @@ -99,7 +99,13 @@ Read from the workflow files in each repo's default branch. | `compiler` (cont.) | `mz check ../examples/connectivity_bar.mz`, then `mz check` over **every** file in `primitives/`, using the built binary rather than the test harness | | `secret scan` | `gitleaks detect` at `fetch-depth: 0` — full history, because this repo arrived via `git subtree split` and every commit reached CI for the first time at once | -`mzizi-lang-benchmark-dispatch.yml` is the second workflow. +`mzizi-lang-benchmark-dispatch.yml` is the second workflow. On push to +`main` it dispatches to a held-out benchmark runner named by the +`MZIZI_HELDOUT_REPO` repo variable, guarded by +`if: github.repository == 'mzizi-dev/mzizi'` so a fork cannot fire it. The +held-out task set is deliberately kept out of the public repo — per +`MIGRATION.md` §5, it is "withheld so the benchmark measures the language +rather than memorisation". Nothing here checks whether that runner exists. The `mz check` steps are the interesting part and are worth copying in spirit: they assert the *shipped binary* still accepts the corpus, which is a From b34349f09a57e4f996ec4a27b04a351f22357684 Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Fri, 11 Sep 2026 21:57:19 +0800 Subject: [PATCH 3/5] fix(ci): split test-args into an array instead of an unquoted expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit actionlint's shellcheck pass failed on `cargo test $EXTRA` with SC2086. The word splitting was intentional — test-args is an argument list — but the unquoted form also glob-expands, which was not intentional, so the warning was correct and the original comment defending the line was wrong. `read -ra` splits and does not glob. An empty input yields an empty array, which is safe under `set -u` from bash 4.4 onward; ubuntu-latest ships bash 5. Worth recording how this got through: actionlint was run locally before pushing and reported clean, because shellcheck is not installed on this machine and actionlint silently skips the shellcheck rules when the binary is missing. A clean local actionlint is therefore weaker evidence than it looks. The CI job, which runs on a runner that has shellcheck, is the one that counts. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/reusable-rust-ci.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-rust-ci.yml b/.github/workflows/reusable-rust-ci.yml index 3bd2ace..f777da2 100644 --- a/.github/workflows/reusable-rust-ci.yml +++ b/.github/workflows/reusable-rust-ci.yml @@ -132,10 +132,16 @@ jobs: working-directory: ${{ inputs.working-directory }} env: EXTRA: ${{ inputs.test-args }} - # Unquoted on purpose: EXTRA is a caller-supplied argument list and - # must word-split. It comes from the calling workflow file, not from - # PR-controlled input. - run: cargo test $EXTRA + # `test-args` is an argument LIST, so it has to word-split — but + # `cargo test $EXTRA` unquoted also glob-expands, and shellcheck is + # right to flag it (SC2086). Splitting into an array does the one + # thing that is wanted and not the one that is not. An empty EXTRA + # yields an empty array, and `"${extra[@]}"` on an empty array is + # safe under `set -u` in bash 4.4+ — ubuntu-latest ships bash 5. + run: | + set -euo pipefail + read -ra extra <<< "$EXTRA" + cargo test "${extra[@]}" # The step that catches what a host build cannot. Runs last so a # formatting or lint failure is reported before the slower compile. From 707204efaf74361eefbc8346854fbd8f20e9f28c Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Fri, 11 Sep 2026 21:58:12 +0800 Subject: [PATCH 4/5] docs: correct the has_wiki and licence counts The settings sweep behind those two rows iterated eight repos and missed mzizi-site, so the counts were taken over a sample that did not include it. mzizi-site has has_wiki false and an Apache-2.0 licence, which moves wikis to four-off/five-on and licences to seven-of-nine. Small numbers, but this page's whole claim is that it reports what is there rather than what is expected, so an off-by-one from an incomplete sweep is exactly the defect it should not have. Co-Authored-By: Claude Opus 5 (1M context) --- ORG_STANDARDS.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ORG_STANDARDS.md b/ORG_STANDARDS.md index d6f8aa1..f16fbf1 100644 --- a/ORG_STANDARDS.md +++ b/ORG_STANDARDS.md @@ -312,8 +312,8 @@ Uniform across all nine unless noted. | `merge_commit_message` | `PR_TITLE` | The PR title becomes the body | | `delete_branch_on_merge` | **true** on all nine | Already correct — no cleanup needed | | `allow_auto_merge` | true on `mzizi-registry`, `mzizi-api-gateway`, `agent-tools`; **false** on the other six | Inconsistent | -| `has_wiki` | false on `mzizi`, `mzizi-registry`, `mzizi-api-gateway`; **true** on the other six | Unused surface, on by default | -| Licence | Apache-2.0 on six; **none** on `mzizi-roadmap` and `.github` | | +| `has_wiki` | false on `mzizi`, `mzizi-registry`, `mzizi-api-gateway`, `mzizi-site`; **true** on the other five | Unused surface, on by default | +| Licence | Apache-2.0 on seven; **none** on `mzizi-roadmap` and `.github` | | | Secret scanning | **enabled on 2 of 8 public repos** — `mzizi-registry`, `mzizi-api-gateway` | | | Secret scanning push protection | Same two | | | Dependabot security updates | **disabled on all nine** | | @@ -430,5 +430,6 @@ 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 six repos leaves an unused, unwatched -surface open on a public org. +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. From 0fc4c471ab333a831ad91a569a33e5100e79739d Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Fri, 11 Sep 2026 21:59:12 +0800 Subject: [PATCH 5/5] docs: record that org-level SHA pinning and token defaults are unset This repo pins third-party actions by SHA, but GitHub has an org setting that enforces it (`sha_pinning_required`) and it is false. A convention that only lives in review comments is weaker than one the platform checks, so the setting belongs in the gap list next to the convention. Noting honestly that it cannot simply be switched on: every existing workflow in the org uses floating tags, so enabling it would break them all until they are pinned. The same endpoint shows `default_workflow_permissions: write`, which gives every workflow a read-write token by default. Recorded alongside it because it is the same one-line admin decision and the same argument. Co-Authored-By: Claude Opus 5 (1M context) --- ORG_STANDARDS.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ORG_STANDARDS.md b/ORG_STANDARDS.md index f16fbf1..66f6d0f 100644 --- a/ORG_STANDARDS.md +++ b/ORG_STANDARDS.md @@ -429,6 +429,20 @@ 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." +**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 +`allowed_actions: all`). This repo pins its third-party actions by SHA as a +convention, but a convention is only as good as the next contributor's +memory — the org setting makes it a rule that GitHub checks. Turning it on +would first require every existing workflow in the org to be pinned; today +they use floating tags (`actions/checkout@v5`, `pnpm/action-setup@v4`, +`dtolnay/rust-toolchain@stable`), so this is a migration, not a switch. The +same endpoint's `default_workflow_permissions: write` is also worth +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