# FEEDBACK — mgf-fed

> Consumer-facing feedback queue for `mgf-fed` (DOC-10 / DOC-13). File friction, requests, and
> cross-project asks here (or via `mgf-fed feedback-howto`). Federation-wide concerns go to
> `mgf-common`'s FEEDBACK.md, not here (AG-11).

**Lifecycle (FB-01..04):** `OPEN` → `ACCEPTED` / `SCHEDULED` / `DEFERRED` / `❌ REJECTED` → `✅ SHIPPED`.
Each entry: what hurts · why · evidence (`file:line`) · suggested shape. A release that ships an
entry flips it to `✅ SHIPPED` with a one-paragraph retrospective (REL-07) + a consumer notification (REL-09).

---

### `PAPER-128` — Consumer apps sit outside every CI-currency gate: `drift-check`'s roster excludes them and `catch-up`'s Woodpecker check is presence-only

| Field | Value |
|---|---|
| Status | OPEN |
| Severity | 🟡 High |
| Submitter | hrb-app (commit `5c60339`) |
| Submitted | 2026-07-30 |
| Decision | pending |
| Decided on | — |
| Scheduled for | — |
| Shipped in | — |

**Concern.** A consumer application's committed pipeline can rot indefinitely while every gate it can see stays green. `drift-check`'s generation-currency and gate-floor checks iterate registry members — `src/mgf/fed/_drift.py:588` (`for m in members(root)`), where `members()` enumerates `MGF_LIBRARIES.json` *libraries* — and consumer apps are registry-external by design. The deliberate backstop, `ci-ungoverned` (PAPER-122 resolution), is scoped to cloned `mgf-*` repos that ship **no** pipeline (`_drift.py:537-543`), so an app that HAS a pipeline — however stale — never matches (hrb-app is additionally not `mgf-*`-named). Meanwhile the one check consumer apps DO run, `catch-up`'s Woodpecker check, declares itself "a **presence** check only" (`src/mgf/fed/cli/_catch_up.py:3499`; ✅ whenever `.woodpecker.yml` or `.woodpecker/*.yml` exists, `:3514-3523`). The union of the three is a structural hole: no tool anywhere compares a consumer app's committed pipeline against the currently-generated one.

**Why it matters.** Proven, not hypothesized — hrb-app, 2026-07-29. Its generated-era pipeline predated the clone-hardening re-render (mgf-standard PAPER-109/PAPER-128 family): with no explicit `clone:` block it inherited Woodpecker's default — a partial clone on the repo owner's ~1 h OAuth netrc credential — so pushes red at the clone step (`could not read Username`, exit 128, every real step skipped) for weeks, while `catch-up` reported the CI check ✅ throughout and `drift-check`'s fleet re-render reached only rostered libraries. The fix arrived by a human diffing against a freshly generated sibling pipeline and pinning the canonical hardened SSH clone block by hand; the in-file comment records the hole verbatim: "hrb-app is a consumer app, so it sat outside `mgf-fed drift-check`'s roster and never got the fleet re-render — filed upstream". This paper is that filing. PAPER-123 already established the principle for outboxes: consumer apps are the tier that hits the sharpest edges first and exactly the tier the gate cannot see — and a green check over a rotten state is worse than no check, because it certifies health.

**Evidence.** `src/mgf/fed/_drift.py:19` (`from mgf.fed._roster import … members`), `:588` (member loop), `:599` + `:537-543` (`ungoverned_repos` — registry-external scan deliberately limited to `mgf-*` repos with no pipeline); `src/mgf/fed/cli/_catch_up.py:3490-3523` (`_check_woodpecker_ci` — "This is a **presence** check only"). The incident: hrb-app `.woodpecker.yml` had no `clone:` block until commit `8696438` (2026-07-29, "ci: pin the hardened SSH clone block (PAPER-109/128)") with postmortem in `f6525af`. Cross-refs: PAPER-121/122 (gate floor + `ci-ungoverned`, both member/`mgf-*`-scoped), PAPER-123 (the same blindness, shipped fix, for feedback outboxes), PAPER-118 (`ci-heal` — adjacent but distinct: it restarts a *flaked* clone; a *stale clone block* reds deterministically and no restart helps).

**Suggested shape.**

**Approach A (recommended) — roster consumers for the pipeline-currency gates.** Either the registry (or fleet policy) grows a `consumers` section, or `drift-check` accepts `--also <path>` — or, cheapest and precedented, reuse PAPER-123's shipped discovery: walk federation-root siblings that are real checkouts carrying a `.woodpecker.yml` and hold them to the clone-block and gate-floor currency checks, no registry edit needed (a new consumer app is covered the day it is cloned).

**Approach B — `catch-up`'s Woodpecker check learns the clone-block currency test.** The exact comparison `drift-check` already performs, run against the repo's own committed pipeline, so the check an app runs in its own repo stops certifying presence as health.

Either closes the hole; both is defense-in-depth — A sees the fleet centrally, B tells the app in its own terminal.

**Cost if deferred past v1.0.** Every future template hardening — and PAPER-109's was credential-handling — silently strands the entire app tier, each app rediscovering a centrally-known fix through its own multi-day red-CI archaeology. The failure compounds with fleet size and is invisible by construction: the tools' own reports say ✅ while it happens.

**Decision rationale.** _Filled when triaged._

**Retrospective.** _Filled when shipped._


### `PAPER-127` — `catch-up --apply` promises to migrate the legacy CLAUDE.md marker and never does (21 repos, non-converging)

| Field | Value |
|---|---|
| Status | ✅ SHIPPED |
| Severity | 🟠 High |
| Submitter | Court (2026-07-29 fleet re-render wave) |
| Submitted | 2026-07-29 |

**Concern.** The `docs/claude/CLAUDE.md` check reports:

> auto-block carries the LEGACY `mgf-common catch-up` BEGIN marker (pre-0.47 adoption — PAPER-04).
> **Run with `--apply` to refresh the block in place and migrate the marker (no duplicate block is written).**

Running `--apply` refreshes the block and **does not migrate the marker**. Re-running reports the
identical warn. It is **non-converging**: `--apply` can be run any number of times and the check
never goes green.

**Mechanism.** The affected files contain **two** auto-blocks. `mgf-alembic/docs/claude/CLAUDE.md`:

- **line 1** — `<!-- BEGIN mgf-common auto-generated — managed by \`mgf-fed catch-up\`.` (canonical)
- **line 298** — `<!-- BEGIN mgf-common auto-generated — managed by \`mgf-common catch-up\`.` (legacy, orphaned)

`_claude_md_block_start()` finds the **first** marker, so `--apply` refreshes the top block
correctly and never touches the orphan below. The detector then still finds
`CLAUDE_MD_LEGACY_BEGIN_MARKER` anywhere in the content (`_catch_up.py:1856,1864`) and re-fires.
So the writer is first-match and the checker is any-match — **the two disagree about what "the
block" is**, and the gap between them is a permanent warn.

The message's parenthetical *"no duplicate block is written"* is true and beside the point: the
duplicate is already there, and nothing removes it.

**Scale.** Exactly **21 of 44** repos. This is the entire remaining `docs/claude/CLAUDE.md`
population in the 2026-07-30 fleet tracker — after a full 41-repo re-render wave took the fleet
from 90 real gaps to 28, these 21 are what is left, and no amount of `--apply` will clear them.

**Why it matters.** A fix-verb that reports success while changing nothing teaches operators that
the warn is noise. It also makes the conformance tracker permanently non-green for a defect that
is one code change away, which is the fastest way to get a gate ignored.

**Evidence.** `_catch_up.py:57-62` (`_claude_md_block_start` — first match wins);
`_catch_up.py:1856` and `:1864` (checker uses `LEGACY_BEGIN_MARKER in content` — any match);
`_catch_up.py:2717-2727` (`_apply_refresh_claude_md` — "a legacy marker is refreshed in place",
emits the current marker for the block it found). Reproduced on `mgf-alembic` (canonical block at
line 1, legacy at line 298) after two consecutive `--apply` runs, both reporting the same warn.

**Suggested shape.** `--apply` should (a) find **all** auto-block markers, canonical and legacy,
(b) refresh the first and **delete the orphans**, and (c) if deletion is too aggressive to do
silently, report the orphan's line number and refuse — either is honest; the current behaviour is
not. Minimum viable: when the checker's any-match finds a marker the writer's first-match did not
handle, say so explicitly instead of claiming `--apply` will fix it.

**Decision rationale.** ACCEPTED and fixed same session — see the commit that flips this row.


### `PAPER-126` — `catch-up --apply` overrides a documented CFM-04 decline, and corrupts a comment doing it

| Field | Value |
|---|---|
| Status | ✅ SHIPPED |
| Severity | 🔴 Blocker-adjacent |
| Submitter | Court (2026-07-29 fleet re-render wave) |
| Submitted | 2026-07-29 |

**Concern.** Running `mgf-fed catch-up --apply` in **mgf-standard** adds
`mgf-test-supervisor>=0.1.2,<0.2` + `pytest-timeout` to `[project.optional-dependencies].dev`
— a dependency that repo **explicitly and permanently declines**, with the rationale written
in the three comment lines immediately above the insertion point:

> `# mgf-standard -> mgf-test-supervisor, and since mgf-test-supervisor -> mgf-common ->`
> `# mgf-standard it closes a CYCLE that the REL-13 registry generator would record …`
> `# we consciously decline TS-23 here … keeping mgf-standard a PURE LEAF with zero mgf-* deps.`
> `# Documented as a CFM-04 decline-with-rationale …`

`--apply` read past its own explanation and applied the fix anyway. **Committing that output
would close the standards/cornerstone dependency cycle in the repo that defines the corpus.**

**It also corrupts the comment.** The insertion appends a comma to the last *comment* line:

    - # docs/standards/principles/non-adoption-is-valid.md).
    + # docs/standards/principles/non-adoption-is-valid.md).,

TOML still parses (the comma is inside a comment), so **nothing catches it** — the list-append
logic assumes the final line before `]` is an item and never checks whether it is a comment.

**Why it matters.** `--apply` is marketed as *mechanical* and was run across 41 repos in one
wave. A mechanical tool that silently overrides a declared, reasoned decline is not mechanical
— it is opinionated in a direction nobody reviewed. This was caught only because the wave
committed `AGENTS.md` + `CLAUDE.md` by explicit path and the pyproject change was left behind
as unexplained working-tree dirt. **A `git add -A` in that loop would have shipped it.**

**Evidence.** `mgf-standard/pyproject.toml:52-63` (the decline rationale + the injected deps,
reverted). The federation already encodes this decline elsewhere:
`mgf-standard/scripts/fleet_conformance_audit.py:63` lists `"mgf-standard": ("mgf-common pin",
"Federation test supervisor", "Federation marker taxonomy")` as declared N/A — **the audit
knows; `--apply` does not.** `mgf-secrets` showed the same class (TS-19 marker taxonomy
injected, reverted).

**Suggested shape.** (a) `--apply` reads the same decline source the audit does — a repo's
`docs/inprogress/MGF_STANDARDS_CONFORMANCE.md` CFM-04 declines — and **skips-with-a-message**
rather than applying. (b) Failing that, `--apply` must refuse to modify `pyproject.toml`
dependency lists at all and only report. (c) Independently: the list-append helper must not
treat a trailing comment line as an item.

**Decision rationale.** ACCEPTED and fixed same session — see the commit that flips this row.


#### `PAPER-01` — mgf-fed discovery is hardcoded to the mgf family; a second family needs a fork

| Field | Value |
|---|---|
| Status | ACCEPTED (2026-07-10 — plan in Resolution below) |
| Severity | 🟡 High |
| Submitter | sooth family — cross-family reuse review |
| Submitted | 2026-07-01 |

**Concern.** mgf-fed's federation discovery is bound to mgf-specific paths, an mgf-shaped
registry schema, and a hardcoded cornerstone repo. A second family (e.g. the Zig-language
`sooth` fleet) cannot point mgf-fed at its own fleet without forking `_roster.py` and a few
call sites. The generalization seams are few and visible — but today they are literals, not
config, so a second adopter has no supported entry point.

**Why it matters.** mgf-fed is otherwise a clean, reusable cross-repo orchestrator (roster,
release waves, static gates, CI generation) that a second family would want to adopt whole.
Every hardcode is a place a fork diverges and then rots. The coupling is broad but the
*load-bearing* bindings are concentrated in a handful of spots — so this is a small, high-leverage
change rather than a rewrite. As a coupling indicator, `mgf-common` appears on 276 lines across
24 files and `mgf-standard` on 105 lines across 22 files in `src/`, yet only four seams below
actually block a non-mgf fleet.

**Concrete evidence.**
- **Registry path is a literal.** `src/mgf/fed/_roster.py:30` — `return root / "mgf-standard" / "MGF_LIBRARIES.json"`. The federation root is already env-resolvable (`MGF_ROOT`, `_roster.py:22`), but the standard-repo name and registry filename are not.
- **The roster loader reads mgf-shaped keys only.** `_roster.py:70` — `data.get("release_waves", [])`; `_roster.py:73` — `for lib in data.get("libraries", [])`; `_roster.py:74` — `repo = str(lib["repo"])`. Sooth's registry (`sooth-standard/SOOTH_LIBRARIES.json`) uses `repos[]` with `name`/`slug`/`ring`/`level`/`deps[]` and has **no** `release_waves` and **no** `libraries` — so `members()` returns an empty list against it, silently.
- **Cornerstone identity is a hardcoded comparison.** `src/mgf/fed/_woodpecker.py:425` — `is_cornerstone=member.repo == "mgf-common"` (echoed by `src/mgf/fed/cli/_ping.py:43` — `_CORNERSTONE = "mgf-common"`). Cornerstone-only CI flags (`_woodpecker.py:133-152`) all key off this literal.
- **Gate/release runners assume Python + ruff + wheels.** `src/mgf/fed/_gates.py` composes ambient `ruff` (the `ruff>=0.4,<0.16` pin) as the fleet-wide lint+format gate; `src/mgf/fed/_index.py:19,34` match/glob `*.whl`; `src/mgf/fed/_woodpecker.py:252` emits `dist/{name}-...-py3-none-any.whl`. A Zig family has neither ruff nor wheels.
- **Coupling indicator (grep across `src/`).** `mgf-common` = 276 matching lines / 24 files; `mgf-standard` = 105 matching lines / 22 files.

**Suggested shape.** One change-set makes mgf-fed genuinely multi-family. Keep the mgf values
as defaults so nothing regresses:
1. **`Family` config seam in `_roster.py`** — `(federation_root, standard_repo, registry_file, cornerstone)` resolved from env/dotfile (extend the existing `MGF_ROOT` pattern), replacing the hardcoded `_registry_path` literal.
2. **Normalized registry adapter** — a per-family loader that maps an arbitrary registry JSON onto the existing `Member` dataclass, and **derives release waves topologically from `deps`** when a family declares no explicit `release_waves` (sooth's case).
3. **Per-language runner seam** — gate/build/verify commands keyed by `lang`, so `gates`/`release` stop assuming Python+ruff+wheels (Zig → `zig build` + its own fmt/verify path).
4. **De-hardcode the cornerstone** — `member.repo == family.cornerstone` in place of `== "mgf-common"` (and the `_ping.py` constant).

This is the single seam a second adopter needs; a short **`docs/` note on the `Family`-config
seam** (how to point mgf-fed at a non-mgf registry + register a language runner) would be the
highest-value artifact for that adopter.

*Filed by the sooth family during a cross-family reuse review — feedback, not a fork request.
mgf-fed's orchestrator design is the pattern sooth wants; this paper is about the seams that
would let a second family adopt it without diverging.*

**Resolution (2026-07-10, queue sweep).** Verified fresh: every cited hardcode still stands —
`src/mgf/fed/_roster.py:30` (registry-path literal), `_roster.py:70-74` (mgf-shaped keys),
`src/mgf/fed/_woodpecker.py:509` (`is_cornerstone=member.repo == "mgf-common"`),
`src/mgf/fed/cli/_ping.py:46` (`_CORNERSTONE = "mgf-common"`), and the Python+ruff+wheels
assumptions in `_gates.py`/`_index.py`. **ACCEPTED** — not a sweep-sized change: the seam
touches roster, generator, gates, release and docs together and needs sooth-side registry
fixtures + a design sign-off. Concrete plan (the paper's own shape, ordered): (1) a `Family`
config dataclass in `_roster.py` (`federation_root`/`standard_repo`/`registry_file`/`cornerstone`,
env+dotfile resolved, mgf values as defaults); (2) a registry adapter mapping arbitrary JSON onto
`Member` + topological wave derivation from `deps` when `release_waves` is absent; (3) a
per-`lang` runner table for gate/build/verify commands; (4) `member.repo == family.cornerstone`
replacing both literals; (5) a `docs/` adoption note. Steps 1+4 are independently landable first.

---

#### `PAPER-02` — generated cornerstone CI installs `example_app` un-steered (dependency-confusion)

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-10 — fixed via mgf-standard `c6f0523`, re-rendered `d0bb82b`) |
| Severity | 🟡 High |
| Submitter | mgf-fed supervisor (cockpit) |
| Submitted | 2026-07-03 |

**Concern.** The generated cornerstone pipeline installs the in-repo example app with a bare
`pip install --quiet -e "./example_app[dev]"` — **no `--index-url` steering** — immediately
after a properly-steered main install. `example_app` depends on `mgf-common`, so a bare
resolve pulls `mgf-common` (+ any mgf-* deps) from **public PyPI** (dependency-confusion /
stale-version) — the exact hazard `LESSON_LEARNED.md` (2026-06-18) fixed for the main install
via `_woodpecker._steered`, but the `example_app` sub-install was never wrapped.

**Why it matters.** mgf-fed's own `drift-check` flags it (`✗ mgf-common (2)` →
`[ci-unsafe-install] 1 bare pip install … resolves mgf-* deps from public PyPI`). It's on the
**cornerstone's** example-app test — the very step meant to validate a real consumer install —
so a transient public-PyPI stale `mgf-common` would silently test against the wrong version.

**Evidence.** `mgf-common/.woodpecker.yml:223` (`- pip install --quiet -e "./example_app[dev]"`,
bare) vs the steered main install one line up (`:222`, `--index-url …/magogi/prod/+simple/` +
retry-same-command). Generated artifact (provenance marker at top). Steering helper already
exists: `src/mgf/fed/_woodpecker.py` `_steered` (LESSON_LEARNED 2026-06-18).

**Suggested shape.** Route the `example_app` sub-install through the same index-steering the
generator applies to the main install (`_woodpecker._steered` / the `extra_index` knob),
including the `cmd || (sleep 15 && cmd)` retry — never a bare `||` fallback. Re-scaffold
mgf-common.

**Resolution (2026-07-10, queue sweep).** ✅ Shipped upstream and verified live. The canonical
fragment now steers the sub-install with the same-command retry:
`mgf-standard/templates/woodpecker/frag-cornerstone.yml:17` —
`pip install --quiet -e "./example_app[dev]" --index-url "$DEVPI_URL" || (sleep 15 && …same…)`
(mgf-standard commit `c6f0523` "fix(ci-template): steer the cornerstone example_app install
(PKG-06)"). mgf-common re-rendered it: `.woodpecker.yml:243` (commit `d0bb82b` "ci: regenerate
pipeline — steered example_app install (PKG-06)"). No bare fallback remains; the retry re-runs
the steered command.

#### `PAPER-03` — drift-check's `.gitleaks.toml` `[exact]` rule is too strict for a per-repo-customized file

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-10 — `95e14a6` + `bc8dabd`, verified vs the live fleet) |
| Severity | 🟢 Nice-to-have |
| Submitter | mgf-fed supervisor (cockpit) |
| Submitted | 2026-07-03 |

**Concern.** `drift-check` compares each repo's `.gitleaks.toml` **byte-exact** to
`templates/gitleaks.toml`. But `.gitleaks.toml` legitimately carries **repo-specific allowlist
paths** (mgf-fastapi allowlists a test-webhook fixture; mgf-common + mgf-standard allowlist
`.claude/`). Any such justified *addition* reads as `[exact] .gitleaks.toml differs from
template`.

**Why it matters.** False-positive drift on a file that is *designed* to be per-repo
customized. Two central repos show `✗` for correct, security-neutral carve-outs → drift-check
noise (trains maintainers to ignore it), with no clean resolution (removing the entry
re-introduces false-positive leak flags).

**Evidence.** `mgf-fed drift-check` → `✗ mgf-common (2)` (one item) + `✗ mgf-standard (1)`:
`[exact] .gitleaks.toml differs from template/gitleaks.toml`. The differences are additive
entries under `[allowlist].paths`, not rule changes. Companion: mgf-standard PAPER-116
(adding `.claude/` to the template resolves the two current instances regardless).

**Suggested shape.** Make the `.gitleaks.toml` drift rule shape-aware — tolerate *additive*
entries under `[allowlist].paths` (and repo-specific `[[rules.allowlist]]`) while still
flagging removed/changed canonical rules or a missing `--config`. Keeps the real protection
(the config is present + wired) without policing legitimate per-repo allowlists.

**Resolution (2026-07-10, queue sweep).** ✅ Shipped in mgf-fed `95e14a6` ("feat(drift): gitleaks
policy comparison goes shape-aware") + `bc8dabd` (non-destructive restore keeps consumer
carve-outs): `_gitleaks_shape_drift` at `src/mgf/fed/_drift.py:112` tolerates additive
`[allowlist]` list entries while still failing removed/edited canonical entries;
`gitleaks_restore_text` (`_drift.py:198`) re-appends consumer extras on restore. Verified today
against the live template: `_gitleaks_shape_drift(<repo>/.gitleaks.toml,
mgf-standard/templates/gitleaks.toml)` → conformant (`None`) for mgf-fastapi (its extra
test-webhook carve-out is tolerated), mgf-common and mgf-standard. The companion mgf-standard
PAPER-116 also landed the `.claude/` entry in the canonical template (2026-07-04). Unit
coverage: `tests/unit/test_gitleaks_shape.py`.

#### `PAPER-04` — `catch-up --apply` duplicates a legacy `mgf-common catch-up` CLAUDE.md auto-block instead of migrating it

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-10 — legacy-marker alias + in-place migration, this branch) |
| Severity | 🟡 High |
| Submitter | figrecovery |
| Submitted | 2026-07-09 |

**Concern.** `mgf-fed catch-up --apply` does not recognise a pre-existing
`docs/claude/CLAUDE.md` auto-block written by the OLD `mgf-common catch-up` (its
BEGIN marker reads *"managed by `mgf-common catch-up`"*). Instead of refreshing
the block in place, it **prepends a second**, `mgf-fed`-format managed block —
leaving the file with two full auto-blocks.

**Why it matters.** After `--apply` the repo carried two managed blocks totalling
847 lines, including 24 stale `mgf-common <cmd>` references the new block was
meant to replace. A maintainer must manually dedup — exactly the mechanical toil
catch-up exists to remove — and a less careful run would commit the duplication.

**Evidence.** figrecovery's 2026-07-09 re-sync: its `docs/claude/CLAUDE.md` had a
`mgf-common catch-up`-managed block from an earlier adoption; `mgf-fed catch-up
--apply` prepended a new `mgf-fed` block rather than replacing the legacy one.
Manual dedup (keep the new mgf-fed block + project content, drop the legacy
block) was required before the gate was clean.

**Suggested shape.** On `--apply`, treat the legacy `mgf-common catch-up`
BEGIN/END markers as an alias of the current `mgf-fed` markers — refresh in place
(replace the legacy block) rather than prepending. Alternatively, detect a legacy
marker and emit a one-line migration notice + refuse to double-write.

**Resolution (2026-07-10, queue sweep).** ✅ Shipped (this branch): the legacy
`mgf-common catch-up` BEGIN marker is now an alias — `CLAUDE_MD_LEGACY_BEGIN_MARKER` +
`_find_begin_marker` in `src/mgf/fed/cli/_catch_up.py`; the presence check, `_extract_auto_block`
and `_splice_auto_block` all accept it, and `--apply` refreshes the block IN PLACE while
migrating the marker line to the current `mgf-fed` form (the stale verb never survives a
refresh — no second block is ever prepended). A dedicated audit finding names the legacy-marker
state and routes it to the refresh fix. Tests: `TestLegacyMarkerMigration` in
`tests/unit/cli/test_catch_up.py` (extract/splice/end-to-end `_apply_refresh_claude_md`,
asserting exactly one block + preserved project content).

#### `PAPER-05` — `catch-up`-written AGENTS.md pointer carries a stale "always installed" justification

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-10 — stale justification replaced, this branch) |
| Severity | 🟢 Nice-to-have |
| Submitter | figrecovery |
| Submitted | 2026-07-09 |

**Concern.** The `AGENTS.md` pointer written by `catch-up --apply` justifies the
agents command with *"`mgf-common` is a dependency of this project, so that
command is always installed."* Post-reorg the command is `mgf-fed agents`, and
installing `mgf-common` does **not** put `mgf-fed` on PATH — they are separately
distributed.

**Why it matters.** The reasoning is now false: a repo can depend on `mgf-common`
and still not have `mgf-fed agents` available. An agent reading the pointer is
told the command is "always installed" when it may not be, and the stale text
reads as un-migrated scaffolding from the mechanical `mgf-common`→`mgf-fed` verb
rename.

**Evidence.** figrecovery's `AGENTS.md` after `mgf-fed catch-up --apply`
(2026-07-09) contained the "`mgf-common` is a dependency … always installed" line
while pointing at `mgf-fed agents`. figrecovery installs `mgf-fed[cli]` as a
separate tool (not a project dep), so the justification did not hold.

**Suggested shape.** Update the generated AGENTS.md justification to reflect the
`mgf-fed[cli]` tool install (e.g. "`mgf-fed` is installed as a federation CLI
tool; run `mgf-fed agents`"), and drop the "dependency of this project ⇒ always
installed" reasoning that assumed the pre-0.47 bundled CLI.

**Resolution (2026-07-10, queue sweep).** ✅ Shipped (this branch): the generated text now says
"`mgf-fed` is installed as a federation CLI tool (`pip install 'mgf-fed[cli]'` or `uv tool
install 'mgf-fed[cli]'` — it is distributed separately from the `mgf-common` library
dependency)" — in `AGENTS_MD_CONTENT` and in `_CLAUDE_MD_TEMPLATE`
(`src/mgf/fed/cli/_init_project.py`); the false "dependency ⇒ always installed" reasoning is
gone. mgf-fed's own `AGENTS.md` + `docs/claude/CLAUDE.md` updated to match (catch-up's AGENTS.md
drift check compares against the constant, so the fleet picks the new pointer up on its next
`catch-up --apply`).

### PAPER-117 — `app-doctor` should warn when the AG-03 hook is present but inert

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-10 — see Resolution) |
| Submitter | mgf-standard (PAPER-117 resolution) on 2026-07-10 |
| Submitted | 2026-07-10 |
| Severity | 🟡 Sharp edge |
| Federation paper | relayed by the supervisor from `mgf-standard/pending-feedback/mgf-fed.md` (FB-05) |

- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (PAPER-117 resolution) on 2026-07-10
- **What hurts:** `mgf-fed app-doctor` checks that `.githooks/prepare-commit-msg`
  **exists**, not that it is **active**. A repo that never ran
  `git config core.hooksPath .githooks` — or a pre-commit-framework repo, which
  *cannot* set it (`pre-commit install` hard-errors on `core.hooksPath`; see
  mgf-standard PAPER-117) and hasn't wired the §4b pre-commit path — passes
  doctor while the AG-03 signoff hook silently never fires. figrecovery hit
  exactly this during its 2026-07-09 re-sync.
- **Why it matters:** the local half of AG-18(c) can be silently dead fleet-wide;
  the failure is invisible until the CI `commit-trailers` gate reds a branch
  after the fact (or worse, on repos whose pipeline lacks the fragment).
- **Evidence:** mgf-standard `FEEDBACK.md` PAPER-117 (+ its resolution, this
  wave); `docs/recipes/standardized-git-config.md` §4a/§4b (the two mutually
  exclusive wirings); pre-commit refusal verified on 4.6.0.
- **Suggested shape:** doctor's AG-18 check gains an *active-wiring* probe: PASS
  when `git config --get core.hooksPath` = `.githooks` **or**
  `.git/hooks/prepare-commit-msg` exists (the pre-commit-installed shim); WARN
  (not FAIL — advisory first, per the PAPER-100 staging discipline) when the
  committed hook exists but neither wiring is live.
- **Resolution (2026-07-10, queue sweep):** ✅ shipped (this branch) exactly as suggested —
  `_check_commit_hook` in `src/mgf/fed/cli/_doctor_standards.py` now runs an active-wiring
  probe (`_hook_wiring_active`): PASS when the repo-local `core.hooksPath` resolves to
  `.githooks` (§4a) **or** a `prepare-commit-msg` shim exists in the git *common* hooks dir
  (§4b, worktree-safe via `git rev-parse --git-common-dir`); WARN — never FAIL — when the
  committed hook is INERT, with the two wiring commands in the message. Tests:
  `TestCheckCommitHook` in `tests/unit/cli/test_doctor_standards.py` (hooksPath-wired ok,
  shim-wired ok, inert → warn).
- **Status:** ✅ SHIPPED (2026-07-10)

### PAPER-118 — auto-retrigger a pipeline that reds on the `clone` step alone (PAPER-109(b))

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-10 — see Resolution) |
| Submitter | mgf-standard (PAPER-109 investigation) on 2026-07-10 |
| Submitted | 2026-07-10 |
| Severity | 🟡 Sharp edge |
| Federation paper | relayed by the supervisor from `mgf-standard/pending-feedback/mgf-fed.md` (FB-05) |

- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (PAPER-109 investigation) on 2026-07-10
- **What hurts:** the fleet's known clone credential-injection flake
  (mgf-standard PAPER-109) reds pipelines through no fault of the change, and
  the standing mitigation is a **manual** restart (e.g. mgf-fed pipeline #154
  "re-trigger pipeline (clone step flaked)"). In-step retry is proven
  insufficient — plugin-git already defaults to `attempts: 5` / `backoff: 5s`
  and the flake survives it (the netrc is absent for the step's whole
  lifetime; only a fresh pipeline start re-injects it).
- **Why it matters:** every occurrence costs a human a manual re-trigger and
  erodes trust in red (PAPER-109's "people learn to shrug off reds").
- **Suggested shape:** wherever mgf-fed already polls/drives Woodpecker
  pipelines, detect the signature — failed pipeline whose ONLY failed step is
  `clone`, log matching `could not read Username` — and restart it **once**
  (bounded; a second identical failure stays red for a human). Poll Woodpecker
  (self-hosted, unmetered), never Codeberg commit-status (PAPER-110's rate
  budget).
- **Resolution (2026-07-10, queue sweep):** ✅ shipped (this branch) as a new verb —
  `mgf-fed ci-heal` (`src/mgf/fed/cli/_ci_heal.py`, registered in `_mgfcli.py`), hung on the
  Woodpecker API surface mgf-fed already had (`cli/_ping.py`, now shared via `mgf.fed._http`).
  Detection: a `failure` pipeline whose ONLY failed step is `clone` (v2 `workflows[].children[]`,
  `procs` fallback). Once-bound: skipped when ANY later pipeline exists for the same commit
  (covers our restart AND a human's); a second identical red stays red. Restarts via
  `POST /api/repos/{id}/pipelines/{number}`; polls Woodpecker only; `--dry-run` previews;
  `--repo` scoping, else the registry roster. Honest scope note: the log-content match
  (`could not read Username`) is NOT implemented — the Woodpecker log endpoint returns
  base64 entry arrays and the step-shape signature + once-bound already keeps the restart
  safe; tightening on log content is an optional follow-up. Tests:
  `tests/unit/cli/test_ci_heal.py` (signature, once-bound, dry-run, end-to-end).
- **Status:** ✅ SHIPPED (2026-07-10 — `mgf-fed ci-heal`; log-content tightening optional)

### PAPER-119 — bake the Codeberg API rate-budget into mgf-fed's client (PAPER-110, code half)

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-10 — see Resolution) |
| Submitter | mgf-standard (PAPER-110 resolution) on 2026-07-10 |
| Submitted | 2026-07-10 |
| Severity | 🟡 Sharp edge |
| Federation paper | relayed by the supervisor from `mgf-standard/pending-feedback/mgf-fed.md` (FB-05) |

- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (PAPER-110 resolution) on 2026-07-10
- **What hurts:** the rate-budget discipline lives only as recipe guidance
  (`activate-ci.md` §"Codeberg API rate-budget", mgf-standard PAPER-110) — an
  agent driving `mgf-fed` at automation speed can still hammer Codeberg into
  the hourly 429 wall, because nothing in the client enforces the budget.
- **Why it matters:** the observed failure (mgf-standard PAPER-110 evidence)
  wedged merges for ~an hour after ~9 PR opens + 20s-interval status polls;
  guidance alone doesn't survive an agent that hasn't read the recipe.
- **Suggested shape:** in mgf-fed's Codeberg HTTP layer: (a) a client-side
  steady-rate cap on state-changing calls (a few/min); (b) **exponential
  backoff + jitter on 429** with a hard stop (never retry-hammer); (c) route
  all pipeline-status polling to the Woodpecker API by construction (make the
  Codeberg commit-status call one-shot-only in the API surface); (d) prefer
  SSH git operations where a REST call is avoidable.
- **Resolution (2026-07-10, queue sweep):** fresh-eyes finding first: **mgf-fed has no Codeberg
  REST client at all** — the only HTTP call sites in `src/` are the Woodpecker API
  (`cli/_ping.py`) and the devpi index (`_release.py`); Codeberg writes are printed `tea`/git
  commands a human runs (e.g. `_feedback._pr_commands`), and git rides SSH. Against that:
  **(b) shipped** — new shared helper `mgf.fed._http.request_json` (this branch): exponential
  backoff (5s base, doubling, 120s cap) **+ ±25% jitter** on HTTP 429, honoring a numeric
  `Retry-After`, bounded at 3 retries then a **hard stop** (the last 429 propagates — never a
  retry-hammer). `cli/_ping.py::_api` (and the new `ci-heal`) route through it, so every REST
  call mgf-fed makes — and any future Codeberg call — inherits the budget by construction.
  Tests: `tests/unit/test_http.py`. **(c) holds by construction** — there is no Codeberg
  commit-status client to make one-shot; all pipeline polling/driving is Woodpecker
  (`ping-siblings`, `ci-heal`). **(d) holds** — git operations are SSH; no avoidable REST.
  **(a) ACCEPTED tail** — a steady-rate cap on state-changing Codeberg calls has zero call
  sites to govern today; the standing invariant is that any future Codeberg REST client MUST
  route through `mgf.fed._http`, which is where the cap lands when such a call site appears.
- **Status:** ✅ SHIPPED (2026-07-10 — 429-backoff half + by-construction (c)/(d); (a) steady-rate
  cap is an ACCEPTED tail pending any actual Codeberg call site)

### PAPER-120 — `feedback --relay` crashes on a target with no `FEEDBACK.md` (and half-applies the batch)

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-10 — see Resolution) |
| Submitter | mgf-standard (supervisor relay run) on 2026-07-10 |
| Submitted | 2026-07-10 |
| Severity | 🟡 Sharp edge |
| Federation paper | relayed by the supervisor from `mgf-standard/pending-feedback/mgf-fed.md` (FB-05) |

- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (supervisor relay run) on 2026-07-10
- **What hurts:** relaying to a target repo that has no `FEEDBACK.md` yet
  raises `FileNotFoundError` from `_relay_one` (`_feedback.py:130` reads the
  target file unconditionally) and aborts the whole `--relay` run mid-batch —
  papers earlier in the plan may already be written/moved while later ones are
  not (non-atomic). Hit live on 2026-07-10 relaying mgf-standard's outbox to
  `mgf-cloud-provision` (a real member with no feedback file yet).
- **Why it matters:** the first relay to any newly-onboarded member fails, and
  a half-applied batch leaves the queues inconsistent — the exact silent-loss
  FB-05 exists to prevent.
- **Evidence:** traceback `mgf/fed/_feedback.py:130 → FileNotFoundError:
  …/mgf-cloud-provision/FEEDBACK.md`; `--dry-run` showed a valid 5-paper plan.
- **Suggested shape:** in `_relay_one`, seed a minimal canonical `FEEDBACK.md`
  header (the feedback-md-template shape) when the target file is absent — or
  skip-with-a-loud-warning; and catch per-paper errors and continue, reporting
  failures at the end (per-paper write+move stays atomic).
- **Resolution (2026-07-10, queue sweep):** ✅ shipped (this branch), both halves, in
  `src/mgf/fed/_feedback.py`: (1) `_relay_one` seeds a minimal canonical `FEEDBACK.md` header
  (`_seed_feedback_md`, the feedback-md-template shape) when the target file is absent and says
  so in its report line — the first relay to a newly-onboarded member now works; (2)
  `run_feedback --relay` captures per-paper `OSError`s, keeps going, invalidates that target's
  PAPER-NN counter (self-healing re-read), prints a failure summary and exits non-zero — a bad
  target can no longer abort the batch mid-way, and each paper's write+move stays atomic (the
  failed paper stays in its outbox untouched). Tests:
  `tests/unit/test_feedback.py::test_relay_seeds_missing_target_feedback_md` and
  `::test_relay_continues_past_a_failing_target` (including the clean re-run after the fix).
- **Status:** ✅ SHIPPED (2026-07-10)

### PAPER-121 — `drift-check`/`app-doctor` should assert required pipeline COMMANDS, not just step names (bespoke pipelines fall behind silently)

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-11) |
| Submitter | mgf-standard (WF-13 re-render wave; mgf-standard PAPER-120) on 2026-07-10 |
| Submitted | 2026-07-10 |
| Severity | 🟡 Sharp edge |
| Federation paper | relayed by the supervisor from `mgf-standard/pending-feedback/mgf-fed.md` (FB-05) |

- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (WF-13 re-render wave; mgf-standard PAPER-120) on 2026-07-10
- **What hurts:** repos in `templates/policy.toml [exempt].woodpecker`
  (`mgf-hrb`, `mgf-test-supervisor`, `mgf-website-template`, `mgf-standard`)
  keep hand-maintained pipelines, so `scaffold` never regenerates them and
  `drift-check` skips the generation-currency comparison. A gate added to the
  canonical template therefore reaches every repo EXCEPT them, with no signal.
  `policy.toml`'s `required` list checks step *names*, which stay identical
  while the commands inside them rot.
- **Why it matters:** proven live this wave — after mgf-standard PAPER-115
  added `ruff format --check` to the canonical lint step and the fleet
  re-rendered, all three Python-linting exempt repos still lacked the gate AND
  carried real format drift (including **mgf-standard itself**, the repo that
  authored the gate). Every future gate inherits the same hole.
- **Evidence:** mgf-standard FEEDBACK PAPER-120 (full evidence);
  post-wave `origin/main:.woodpecker.yml` for mgf-hrb / mgf-test-supervisor /
  mgf-standard — `ruff check src tests` present, `ruff format --check` absent;
  `uv run ruff format --check src tests` → 1 drifted file in each.
- **Suggested shape:** add a `required_commands` concept sourced from
  `policy.toml` (e.g. `["ruff check", "ruff format --check",
  "check_commit_trailers.py"]`) and assert it against EVERY repo's
  `.woodpecker.yml` — exempt or not — in `drift-check` (and/or
  `app-doctor --standards`). Exempt means "we don't regenerate your file," not
  "you may silently drop a federation gate." Keep it a substring/step-scoped
  match so bespoke pipelines stay free in shape while staying honest on gates.
- **Resolution (2026-07-11, gate-floor branch):** ✅ shipped, as suggested. New
  `src/mgf/fed/_pipeline.py` defines the **gate floor** — the commands every member's CI
  must actually RUN — and `_drift.gate_floor()` asserts it against EVERY member's
  committed `.woodpecker.yml`, **exempt or not** (a new `ci-command` drift kind).
  `required_commands` is read from the fleet policy when present and DEFAULTS to
  `["ruff check", "ruff format --check", "check_commit_trailers.py"]` when absent, so the
  gate is live before the key lands — **the key still belongs in mgf-standard's policy**
  (`[tool.mgf-fed.policy]` in its `pyproject.toml`, or `templates/policy.toml`), which is a
  different repo and was deliberately not edited from here. Matching is substring, per
  command LINE (`mgf.fed._pipeline.command_lines` — a step *named* `lint` never satisfies a
  gate, and a commented-out command is not a gate), so bespoke pipelines stay free in shape.
  Scoped to `lang == "python"` members: the default list is the Python gate set, and a Zig
  pipeline legitimately has no ruff. `drift-check` reports the floor in its own **GATE
  FLOOR** section. Verified against the real fleet: the four `[exempt].woodpecker` repos'
  `origin/main` pipelines now pass, and a lint step that carries `ruff check` but not
  `ruff format --check` is flagged. Tests: `tests/unit/test_pipeline.py` (20) +
  `tests/unit/test_gate_floor.py` (15) — the latter needs no sibling mgf-standard, so it
  runs in a single-repo CI clone too.
- **Status:** ✅ SHIPPED (2026-07-11)

### PAPER-122 — `app-doctor` should catch the supervisor-coverage ↔ `patch = ["subprocess"]` coupling (green suite, red build)

| Field | Value |
|---|---|
| Status | ✅ SHIPPED (2026-07-11) |
| Submitter | mgf-standard (WF-13 re-render wave; mgf-standard PAPER-121) on 2026-07-10 |
| Submitted | 2026-07-10 |
| Severity | 🟡 Sharp edge |
| Federation paper | relayed by the supervisor from `mgf-standard/pending-feedback/mgf-fed.md` (FB-05) |

- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (WF-13 re-render wave; mgf-standard PAPER-121) on 2026-07-10
- **What hurts:** the canonical coverage step runs the suite through
  `mgf.test_supervisor`, which spawns pytest in CHILD processes. Coverage only
  sees them if the repo's `pyproject.toml` carries `[tool.coverage.run]
  patch = ["subprocess"]` (coverage ≥ 7.10). Without it the build reds with
  **every test passing**: "No data was collected" → `coverage combine` → "No
  data to combine" → exit 1. The pipeline template *documents* the requirement;
  nothing enforces it, and the error names neither the key nor pyproject.
- **Why it matters:** hit three repos in a single wave (`mgf-brand-web`,
  `mgf-cloud-web`, `mgf-fed-web`) the moment they were re-rendered onto that
  step. The signature is maximally confusing (all-green tests, red step), so it
  costs a real debugging session each time; every newly-scaffolded or
  re-rendered repo can walk into it.
- **Evidence:** mgf-brand-web pipeline #31 `test-3.12` log: `[mtest] OK: 39
  passed (39 total)` → `CoverageWarning: No data was collected` → `coverage
  combine` → `No data to combine` → exit 1. Green reference with the key:
  `mgf-http/pyproject.toml`. Fixed per-repo in the wave branches.
- **Suggested shape:** `app-doctor` (or `scaffold`/`drift-check`) FAILS when a
  repo's `.woodpecker.yml` contains `-m mgf.test_supervisor` under a `coverage
  run` while its `pyproject.toml` lacks `[tool.coverage.run] patch =
  ["subprocess"]` — they are a coupled pair (DOC-18-style). Ship the key in the
  cookiecutter by default.
- **Resolution (2026-07-11, gate-floor branch):** ✅ shipped as a **FAIL** (not a warn) in
  three surfaces, all reading the same `mgf.fed._pipeline.coverage_patch_gap`: (1)
  `mgf-fed app-doctor` — a ✗ check with the exact fix as its recommended action; (2)
  `mgf-fed app-doctor --standards` — a `TS-23` **fail** Finding, i.e. it reds the
  `app-doctor` step that the canonical pipeline already runs in EVERY repo, so a
  newly-scaffolded or re-rendered repo hears about the coupling from its own CI; (3)
  `drift-check` — a `ci-coverage` gate-floor item, so the fleet sweep sees it centrally.
  FAIL is justified: both halves of the coupling are read from the repo's own committed
  files (the finding is exact, never a guess), and the consequence is a certain,
  already-observed build failure whose own error message names neither the key nor
  pyproject. Silent when the pipeline does not run the supervisor under coverage, when
  there is no pipeline, or when pyproject cannot be parsed — it never invents a failure.
  Tests: `tests/unit/cli/test_doctor_coverage_coupling.py` (5),
  `tests/unit/cli/test_doctor_standards.py::TestSupervisorCoveragePatch` (5), plus the
  `ci-coverage` cases in `tests/unit/test_gate_floor.py` and `tests/unit/test_pipeline.py`.
  **Not done here:** shipping the key in the cookiecutter — that template lives in
  mgf-standard, a different repo.
- **Resolution (2026-07-11) — the third hole, a repo with NO pipeline at all:** shipped as
  TWO checks, because the reported repo turned out not to be where the report said it was.
  - `ci-missing` — a **registry member** with no `.woodpecker.yml` and no explicit
    `[exempt].no_pipeline` entry is now a gate-floor FAIL in `drift-check`, checked for
    every member regardless of `--shape` (a Zig member with no pipeline runs no gates
    either — the old `--shape python` filter hid it entirely). `[exempt].woodpecker`
    deliberately does NOT excuse it: that list excuses *generation-currency*, and mgf-hrb
    dropping its hand-maintained pipeline would be exactly as bad as never having one.
  - `ci-ungoverned` — **and that check alone would still not have caught mgf-brand.**
    Verified, not assumed: `mgf-brand` is **not a registry member at all**
    (`mgf-standard/MGF_LIBRARIES.json` lists `mgf-brand-qt` and `mgf-brand-web`, never
    `mgf-brand` — it ships no publishable package, so the generator never listed it). Every
    tool we own iterates *registry members*, so nothing was ever looking at it — which is
    the actual root cause of "no gate has ever run on mgf-brand", and a member-scoped check
    reproduces the blind spot instead of closing it. So `drift-check` now also scans the
    federation root for **cloned `mgf-*` repos the registry does not list that ship no
    pipeline**, and fails them (`_drift.ungoverned_repos`). Deliberately narrow — a
    non-registry repo that HAS a pipeline (mgf-website, mgf-cost-watch) is running gates and
    is none of drift-check's business. Verified against the real fleet: exactly **one**
    finding, `mgf-brand`, and no noise. Excusable, like a member, by `[exempt].no_pipeline`.
  Tests: `tests/unit/test_gate_floor.py` (§2 + §4), `tests/unit/test_woodpecker.py`
  (`test_drift_missing_woodpecker_is_a_gate_floor_item` — the old presence-check contract
  deliberately superseded). Neither shells out to `git` (the CI image has no `git` binary),
  so the ungoverned scan detects a checkout by a `.git` path test, not a `git` call.
  **Left to mgf-standard (not editable from here):** registering `mgf-brand`, or recording
  it in `[exempt].no_pipeline` and de-activating it on the forge; and adding the
  `required_commands` key to `templates/policy.toml`.
- **Status:** ✅ SHIPPED (2026-07-11)

### PAPER-123 — `feedback --check` cannot see consumer-app outboxes: the FB-05 no-silent-rot gate is blind to the whole app tier

| Field | Value |
|---|---|
| Status | OPEN |
| Submitter | TelePlasma (rescue round S3.2) |
| Submitted | 2026-07-12 |
| Severity | 🟡 Sharp edge |
| Federation paper | relayed by the supervisor from `TelePlasma/pending-feedback/mgf-standard.md` (FB-05); routed here per the FEEDBACK routing table — sibling CLI behaviour |

- **Severity:** 🟡 Sharp edge
- **Filed by:** TelePlasma (2026-07-10 rescue round) on 2026-07-12
- **What hurts:** FB-05 binds *every* project ("a project is read-only on every
  other federation repo") and its gate promises that "a finding can never rot
  silently". But `mgf.fed._feedback` discovers outboxes through
  `_roster.members()`, which enumerates `MGF_LIBRARIES.json` **libraries**.
  Consumer applications (TelePlasma, vm-vmanager-app, hrb-app, figrecovery …)
  are registry-external **by design**, so their `pending-feedback/` is invisible:
  run from the federation root, `mgf-fed feedback --check` printed
  "**No un-relayed feedback in any outbox ✓**" (exit 0) while TelePlasma's outbox
  held **six** papers — one of them a 🔴.
- **Why it matters:** apps are exactly the consumers FB-05 most wants to hear
  from — they hit the sharpest edges first (this batch of six came out of a
  single app's rescue round) — and they are exactly the tier the rot-gate cannot
  see. A silent gate is worse than no gate: it certifies "drained" over a full
  queue, which is the precise failure FB-05 exists to prevent.
- **Evidence:** `src/mgf/fed/_feedback.py:24` (`from mgf.fed._roster import
  federation_root, members`) → `:75` (`for member in members(base)`);
  `_roster.members()` → `load_registry(root)` → `data["libraries"]`. Verified on
  `origin/main` (mgf-fed 0.11.2) at relay time, 2026-07-12. Reproduced:
  `mgf-fed feedback --check` → exit 0 / "no un-relayed feedback" with
  `TelePlasma/pending-feedback/mgf-standard.md` present and un-relayed. Adjacent:
  **PAPER-120** (relay crashes on a target with no `FEEDBACK.md`) — same code
  path, different blind spot.
- **Suggested shape:** either **(a)** walk a declared consumer list in addition to
  the registry (the "consume releases; catch-up automation only" set already named
  in the federation-automation plan) — closes it fleet-wide; or **(b)**, cheaper
  and composable: when `feedback --check` runs **inside a repo**, scan *that*
  repo's outbox regardless of roster membership, so an app can wire the gate into
  its own CI. If instead the fleet's ruling is that apps sit outside FB-05
  entirely, that is a standards CLARIFY (mgf-standard FB-05) — but then the rule's
  "every project" wording should say so, because today it promises a gate the
  tool does not deliver.
- **Status:** ✅ SHIPPED (2026-07-24) — both halves now exist. Shape **(b)** had already shipped as `--in-repo` (while this entry still read OPEN — the queue-hygiene problem the 2026-07-24 MGF Family review was run to find). Shape **(a)**, the fleet-scan hole, is closed here: `_feedback.scan()` now also walks federation-root siblings that are real checkouts carrying a `pending-feedback/` dir — discovered the same way `_drift.ungoverned_repos` finds ungoverned repos, so a new consumer app is covered with no registry edit. Verified against the real fleet (it reports 7 un-relayed papers of genuine FB-05 debt); 2 tests incl. a negative for a non-checkout directory. Note no non-member source appears *today* only because TelePlasma's outbox is currently drained — the hole was structural, not hypothetical.

### PAPER-124 — `app-doctor --standards` should report graceful N/A on non-Python repos (not error)

| Field | Value |
|---|---|
| Status | OPEN |
| Submitter | mgf-common queue sweep (Court/CTO delegated agent) on 2026-07-10 — carries forward mgf-common PAPER-101 (originally filed by mgf-standard's federation conformance sweep, 2026-06-07), MOVED here because the `doctor` CLI left mgf-common at the 0.47.0 pure-library cutover. |
| Submitted | 2026-07-10 |
| Severity | 🟢 Nice-to-have |
| Federation paper | relayed by the supervisor from `mgf-common/pending-feedback/mgf-fed.md` (FB-05) |

- **Severity:** 🟢 Nice-to-have
- **Filed by:** mgf-common queue sweep (Court/CTO delegated agent) on 2026-07-10 — carries forward mgf-common PAPER-101 (originally filed by mgf-standard's federation conformance sweep, 2026-06-07), MOVED here because the `doctor` CLI left mgf-common at the 0.47.0 pure-library cutover.
- **What hurts:** `mgf-fed app-doctor --standards` requires a `pyproject.toml` with `[project]`; on a **non-Python** federation member it prints "cannot detect project shape" and exits non-zero. The Zig libs (`build.zig.zon`: mgf-common-zig, mgf-common-identity, mgf-hrb-{model,kabi,cap,rt}) and the `mgf-website-template` cookiecutter all hit this. It reads as a failure when the honest result is "N/A — not a Python project."
- **Why it matters:** a federation-wide `app-doctor --standards` sweep shows the non-Python members as errors that are actually just out-of-scope for a Python audit — noisy + misleading, exactly the failure shape `catch-up` already fixed for itself (PAPER-97; see `src/mgf/fed/cli/_catch_up.py:214` "N/A — … (non-Python `docs` repo; PAPER-97)").
- **Evidence:** `src/mgf/fed/cli/_doctor_standards.py` — `doctor_standards()` calls `_discover_project`; on `None` it prints "mgf-fed app-doctor --standards: cannot detect project shape (need a pyproject.toml with [project] name + a src/ layout)…" and returns `EXIT_OPERATION_ERROR` (lines 856-864; verified against mgf-fed main checkout 2026-07-10).
- **Suggested shape:** mirror the PAPER-97 catch-up fix — detect a non-Python member (no `pyproject [project]`; presence of `build.zig.zon` / `cookiecutter.json`) and emit a single "N/A — not a Python project (no Python conformance surface)" finding with exit 0 instead of the shape error. The language-agnostic root-doc facts (DOC-15 ledger, DOC-19 `LESSON_LEARNED.md`) could still be checked; the Python code-level checks (AP-09/AP-13/TS-04/DP-12) are N/A.
- **Status:** OPEN

### PAPER-125 — Verb-name reuse across the 0.47 CLI split makes a stale invocation run-but-wrong instead of fail

| Field | Value |
|---|---|
| Status | OPEN |
| Severity | 🟡 High |
| Submitter | hrb-app (commit `96aeb42`) |
| Submitted | 2026-07-29 |
| Decision | pending |
| Decided on | — |
| Scheduled for | — |
| Shipped in | — |

- **Severity:** 🟡 High
- **Filed by:** hrb-app (commit `96aeb42`) on 2026-07-29 — found during the W1 dependency walk to mgf-common 0.50 (the 0.46→0.50 crossing that performs exactly this rename).
- **What hurts:** at the 0.47 pure-library cutover, `mgf-common doctor` became `mgf-fed app-doctor` and `mgf-common feedback` became `mgf-fed feedback-howto`. But **`mgf-fed doctor` and `mgf-fed feedback` both exist as different commands**: `doctor` is mgf-fed's own OP-02 self-diagnostic ("mgf-fed's own operational prerequisites"), and `feedback` is the FB-05 outbox relay ("relay cross-repo feedback outboxes into target inboxes"), whose no-argument default is `--check`. So the obvious migration — mechanically rewrite `mgf-common <verb>` to `mgf-fed <verb>` — silently retargets two verbs. The consumer asked "audit MY app" and got mgf-fed's own health; asked "print the feedback howto" and got an outbox-rot scan. Both run, both print something plausible, both exit 0. hrb-app's doc sweep rewrote ~30 prose call sites across the root docs and `docs/`, and only caught the two collisions by cross-checking every rewritten verb against `--help`.
- **Why it matters:** every consumer crossing 0.46→0.47+ performs this rename, and it is the one migration step a human is most likely to delegate to `sed`. The failure is invisible to every gate: the commands exist, the arguments parse, the exit code is 0, and the output is *shaped* like a diagnostic report. The two flagged forms fail honestly — `mgf-fed doctor --standards` exits 2 on an unrecognized argument, because `doctor` takes only `--root` — so the trap is precisely the **bare** forms, which is also the form that appears in prose docs and agent briefings. Silent-wrong beats loud-broken only when it is actually right.
- **Evidence:** `mgf-fed --help` lists `doctor`, `feedback`, `app-doctor`, and `feedback-howto` as four separate subcommands (verified against mgf-fed 0.16.0 as installed by hrb-app, 2026-07-29): `doctor` → "self-diagnostic — mgf-fed's own operational prerequisites (OP-02)"; `feedback` → "relay cross-repo feedback outboxes into target inboxes (FB-05)"; `mgf-fed feedback --help` → `[--check] [--relay] …` with `--check` documented as "the gate; default"; `mgf-fed doctor --help` → `[--root ROOT]` only. Consumer-side record of the rename sweep: `hrb-app/docs/inprogress/MGF_ADOPTION.md:82` ("All prose across the root + `docs/` docs rewritten from `mgf-common <verb>` to `mgf-fed <verb>`"). Same migration family as PAPER-04 / PAPER-05 (legacy `mgf-common catch-up` artifacts), different mechanism: those are about stale *content*, this is about a stale *invocation* that still works.
- **Suggested shape:** **(A, recommended)** make the legacy intent detectable and say so once. When `mgf-fed doctor` runs with a consumer-app-shaped cwd (a `pyproject.toml` whose `[project]` name is not `mgf-fed`), or `mgf-fed feedback` runs with no outbox in scope and no explicit flag, print one disambiguation line before proceeding — e.g. `note: this is mgf-fed's own self-diagnostic; for a consumer app you probably want 'mgf-fed app-doctor'` — and mirror it for `feedback` → `feedback-howto`. Cheap, additive, no rename, and it lands in the exact terminal where the mistake is being made. **(B)** rename the newer commands (`self-doctor`, `feedback-relay`) and let the legacy names hard-error with a pointer for one minor cycle — cleanest semantically, but it breaks the fleet's own committed pipelines that already call `doctor`/`feedback`, so A first. Either way, a one-line "renamed verbs, and the two names that were reused" table in the 0.47 migration notes would have caught this before the terminal did.
- **Cost if deferred past v1.0:** every future consumer migration pays the same audit tax, and the ones that skip the audit ship docs and agent briefings that confidently tell the next reader to run the wrong command. The cost is not the wrong output once; it is the wrong instruction persisted into a repo's documentation, where nothing will ever fail to reveal it.
- **Status:** OPEN
