# Contributing to `mgf-standard`

## TL;DR

- The biggest contribution here is a **standards-rule change** — read the
  section below before touching `docs/standards/`.
- `uv sync --extra dev` to set up.
- **Canonical validation entrypoint** (AG-21) — `mgf-fed local-ci mgf-standard`.
  It runs this repo's *real* pipeline in containers. Its divergences from remote
  CI are declared below under [`## CI delta`](#ci-delta); nothing else diverges
  silently.
- **Fast local loop** — the five static gates, green before opening a PR:
  ```bash
  uv run ruff check src tests scripts
  uv run ruff format --check src tests scripts
  uv run mypy --strict src/
  uv run pytest
  uv run lint-imports
  ```
  This loop is a **subset** — see [`## CI delta`](#ci-delta) for what it does not
  cover. "Validated" means the canonical entrypoint passed; a green fast loop is
  reported as **partial**.
- Vulnerabilities go to the private channel in [`SECURITY.md`](SECURITY.md),
  not the public issue tracker.

---

## Dev setup

```bash
git clone ssh://git@codeberg.org/magogi-admin/mgf-standard.git
cd mgf-standard
uv sync --extra dev
```

Verify the five fast gates are green on a clean checkout before making any change.

### Note on the test runner (TS-23 carve-out)

This repo does **not** dev-depend on `mgf-test-supervisor`. That edge would
create a registry dependency cycle (`mgf-standard` → `mgf-test-supervisor`
→ `mgf-common` → `mgf-standard`). The test surface here is trivial; we
run plain `pytest`. See `pyproject.toml` for the full rationale comment and
`docs/inprogress/MGF_STANDARDS_CONFORMANCE.md` for the CFM-04 decline record.

---

## CI delta

> **[AG-21](docs/standards/AGENTS.md#AG-21).** Every divergence between what you can
> run here and what CI runs is declared below, with its reason. An **undeclared**
> divergence is the defect — it is what lets "it passed locally" and a red pipeline
> both be honest. This section has a fixed heading so it is greppable; keep it in
> lockstep with `.woodpecker.yml` **in the same commit** that changes a step
> (DOC-18 §10.4).
>
> **Why here and not in the pipeline header:** `.woodpecker.yml` is a generated
> artifact under **WF-13** (this repo is `policy.toml [exempt].woodpecker`, but the
> rule picks one home for every repo). A hand-written header comment is discarded by
> the next `mgf-fed scaffold`, and a delta list a routine regeneration deletes is
> worse than none — its absence reads as "no deltas."

**Canonical entrypoint:** `mgf-fed local-ci mgf-standard` — `woodpecker-cli exec`
against the committed pipeline, so it runs the *real* steps, not a re-description of
them. **Fast local loop:** the five commands in the TL;DR.

| CI step | Canonical entrypoint | Fast loop | Reason for the delta |
|---|---|---|---|
| `lint` (ruff check + `ruff format --check` + the OP-03 placeholder-URL grep + `lint-imports`) | ✅ | **partial** | the fast loop omits the OP-03 `grep` guard — a one-line pipeline-only check with no local equivalent |
| `commit-trailers` | ✅ | ❌ | needs the branch's commit range fetched (`git fetch --depth=100 origin main`); run `python scripts/check_commit_trailers.py` by hand before pushing |
| `typecheck` | ✅ (`mypy src`) | ✅ (`mypy --strict src/`) | **equivalent, not a delta** — `[tool.mypy] strict = true` in `pyproject.toml` means CI's `mypy src` is already strict; the local `--strict` flag is redundant, not stricter. Listed because the *surfaces* differ and a reader will otherwise assume a delta (proven by probe: an untyped def reds both forms identically) |
| `test-3.11` / `test-3.13` | ✅ | ❌ | the fast loop runs one interpreter — whichever `uv` resolved. Version-specific failures surface only in the entrypoint or remote CI |
| `test-3.12` + `coverage --fail-under=80` | ✅ | **partial** | the fast loop runs `pytest` with no coverage gate; the 80 % floor (TS-06/WF-10) is entrypoint-and-CI only |
| `vuln-scan` (`pip-audit`) | ✅ | ❌ | network — fetches the OSV database |
| `secret-scan` (`gitleaks`) | ✅ | ❌ | needs the pinned `zricethezav/gitleaks` image |
| `app-doctor` (`mgf-fed app-doctor --standards`) | ❌ **reds** — see †  | ❌ | needs `mgf-fed[cli]` installed and `$DEVPI_URL` set; it is **not** a dev dependency here (that edge would close the standards ↔ cornerstone cycle — the TS-23 carve-out above) |
| `conformance-tracker` | ✅ | ❌ | stdlib-only and cheap — run `python3 scripts/fleet_conformance_audit.py --check` directly if you touched the tracker |
| `cross-layer-audit` (`mgf-fed audit --check-artifact`, **`mgf-fed` pinned to `==0.24.0`**) | ❌ **reds** — see † | ❌ | reads the committed `AUDIT.json`; producing a fresh one needs **every `mgf-*` repo on disk** plus credentials, so the write half runs where the fleet is, never in a gate. **The auditor is pinned** (2026-08-06): it was installed unpinned until mgf-fed 0.25.0 published to devpi mid-queue, registered two checks the committed artifact predates, and turned every mgf-standard board red without a line of this repo changing. A gate's tools are pinned like its `image:` tag. To reproduce the gate locally you must pin too — `uv run --no-project --with "mgf-fed==0.24.0" mgf-fed audit --check-artifact` from the repo root — because an unpinned local install now diverges from CI in the opposite direction. **Unpin trigger:** `>=0.25.1` **and** regenerate `AUDIT.json` in the same change, as part of the fleet catch-up wave that inherits 0.1.79's scaffold bytes (the step's own comment carries the full reasoning and the deadline: the artifact goes stale 2026-08-19) |
| `fedres-check` | ❌ **reds** — see † | ❌ | needs `mgf-fedres` + `mgf-common[standards]` from devpi |
| `build` + `sbom` | ✅ | ❌ | container-side packaging; `python -m build` locally is equivalent but not part of the loop |
| `publish`, `verify-devpi`, `tag-coherence`, `smoke` | ❌ **even in the entrypoint** | ❌ | **tag-gated** (`refs/tags/v*`). `woodpecker-cli exec` without a tag skips them by design; the publish path is owned by [`docs/recipes/local-release.md`](docs/recipes/local-release.md) → [`remote-release.md`](docs/recipes/remote-release.md) |

**Three deltas the entrypoint carries against remote CI itself**, inherent to local
`exec` (see [`docs/recipes/run-ci.md`](docs/recipes/run-ci.md) §2):

1. **Tag-gated steps never fire** without a tag — the row above.
2. **One shared workspace, not per-step volumes.** `woodpecker-cli exec` runs every
   step against a single workspace on one host. This repo's pipeline has no parallel
   steps that write overlapping paths, so it is unaffected today; a new parallel step
   that shares state must be serialised with `depends_on` or it will behave
   differently locally than remotely.
3. **† `$DEVPI_URL` is not forwarded, so three steps red deterministically.**
   `app-doctor`, `cross-layer-audit`, and `fedres-check` each open with a fail-fast
   guard — `[ -n "$DEVPI_URL" ] || { echo …; exit 1; }` — and `DEVPI_URL` is a
   **Woodpecker server-level variable** (PAPER-113; `MGF_INFRA.md` §2), injected by
   the remote server and absent locally. `mgf-fed local-ci` invokes
   `woodpecker-cli exec` with **no `--env` flags**, so a `DEVPI_URL` exported in your
   shell does **not** reach the container either. These three steps therefore fail on
   their guard line, every time, on every workstation. **This is the entrypoint's
   largest delta and the most important one to know**, because it is the difference
   between "the entrypoint reds" and "my change broke something": a red confined to
   exactly these three steps, on the guard message, is the *expected* local result.

   Until it is fixed, run those three by hand against a real index:

   ```sh
   export DEVPI_URL=https://195.15.203.250.sslip.io/magogi/prod/+simple/
   mgf-fed app-doctor --standards          # needs mgf-fed[cli] installed
   mgf-fed audit --check-artifact
   fedres check                            # needs mgf-fedres + mgf-common[standards]
   ```

   **The fix is cross-repo and is not ours to make** (AG-13): `mgf-fed local-ci`
   should forward the variable (`woodpecker-cli exec --env DEVPI_URL=…`, or read it
   from the environment and pass it through). Recorded as owed to the `mgf-common`
   supervisor in [`docs/inprogress/SOOTH_ADOPTION.md`](docs/inprogress/SOOTH_ADOPTION.md)
   §5. When it lands, this row shrinks to "needs `$DEVPI_URL` exported."

**Prerequisites for the entrypoint:** Docker running, `woodpecker-cli` (or
`woodpecker`) on `PATH`, **and — for the three steps in delta 3 — a `DEVPI_URL` that
actually reaches the container, which today it cannot.** `mgf-fed local-ci` exits `2`
if Docker or the CLI is missing, so those two are loud failures rather than
silently-skipped steps; the `$DEVPI_URL` case is loud in a different way — the step
runs, prints its guard message, and reds. **A full-green local `exec` is therefore not
achievable on this repo today**, and any claim of one should be disbelieved: the
honest local verdict is "green except the three `$DEVPI_URL`-guarded steps."

---

## Standards-rule changes (the primary contribution path)

Every change to [`docs/standards/`](docs/standards/) is a federation-wide
contract change — every `mgf-*` repo and every consumer conforms to these
rules.

**DOC-18 change-propagation requirement:** a standards change MUST update,
in the same commit:

1. The **README *Index of MUSTs*** (`docs/standards/README.md`) if a new MUST
   is added or an existing MUST is reworded.
2. Every **dependent named in the changed doc's Propagation header** — typically
   `mgf-common/docs/claude/CLAUDE.md` and whatever consumer guides reference
   the rule.

Skipping propagation leaves the federation in a split-brain state where the
rule and the docs that cite it disagree. This is the worst failure mode for the
standards-source role.

Additional bar for standards changes:

- Match the existing voice: dense, code-grounded, AS-IS callouts,
  cross-references.
- Bump the topic doc's version per the policy in `docs/standards/README.md`
  §Versioning (MAJOR = new MUST; MINOR = new SHOULD / template; PATCH =
  wording).
- Add the rule to the topic's mechanical-enforcement matrix, or write a
  justification for review-only.

---

## PR process

**Non-trivial work** (new rule MUST, new recipe, new dependency): open an issue
or add a `FEEDBACK.md` entry first. Let discussion converge before writing.
**Trivial work** (typo, wording, single-test bug fix): open the PR directly.

Branch from `main`. Branch name: `<topic>-<short-description>`.

Commits follow [Conventional Commits](https://www.conventionalcommits.org/):
`feat:`, `fix:`, `docs:`, `chore:`, `security:`. Subject ≤ 70 chars; body
explains *why*.

Every commit MUST end with both trailers (no exceptions — AG-03; setup in
[`docs/recipes/commit-signing.md`](docs/recipes/commit-signing.md)):

```
Signed-off-by: Bassam Alsanie <bassam@magogi.org>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
```

The `Signed-off-by` trailer is enforced automatically by the committed
`prepare-commit-msg` hook (it catches `git commit -m`, which bypasses the
`.gitmessage` template). Activate it once per clone:

```
git config core.hooksPath .githooks
```

(Repos that use the **pre-commit framework** must NOT set `core.hooksPath` —
`pre-commit install` refuses when it is set. Wire the same script as a
`prepare-commit-msg`-stage pre-commit hook instead; see
[`docs/recipes/standardized-git-config.md`](docs/recipes/standardized-git-config.md) §4b.)

The hook is idempotent and adds only the constant human signoff; you (or the
drafting agent) still add the `Co-Authored-By:` line naming the model.

PR description must cover: **What** / **Why** / **How** / **Test plan** /
**Standards compliance** (cite the rule ID) / **Breaking changes** (if any,
include the AP-12 four-part migration recipe in `CHANGELOG.md [Unreleased]`).

Maintainer-merge only. Every user-visible change gets a `CHANGELOG.md
[Unreleased]` entry before merge.

---

## Communication

| Channel | What it's for |
|---|---|
| Codeberg issues | Bug reports, rule proposals, public discussion |
| Codeberg PRs | Code + doc changes |
| Codeberg private security advisories | Vulnerability reports (per [`SECURITY.md`](SECURITY.md)) |
| [`FEEDBACK.md`](FEEDBACK.md) entries | Design decisions that need a permanent recorded rationale |

---

## License

By contributing, you agree that your contributions are licensed under the
project's [MIT License](LICENSE).
