# FEEDBACK — mgf-standard

> **Repo-scoped feedback queue** (rules FB-01..FB-04 / DOC-13). This is the
> channel for friction with **the standards, the federation contract, the
> library registry, the federation-process recipes, or the
> `mgf.standard` package** — everything this repo owns.

## Routing — file here when…

| Friction is about… | File at… |
|---|---|
| A standards **rule** (AP/CF/EH/LG/SC/TS/DP/DOC/…): wrong, missing, ambiguous, or a new rule is needed | **here** |
| The **federation contract** (`FEDERATION.md`): a duty is unclear / wrong / missing | **here** |
| The **library registry** (`MGF_LIBRARIES.md`) / its generator / the REL-13 gate | **here** |
| A **federation-process recipe** (cohort release, sibling rename, sync prompt, a template) | **here** |
| The `mgf.standard` package (resolver API, packaging) | **here** |
| `mgf-common` **library code or CLI** behaviour | [`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/FEEDBACK.md) |
| A specific **sibling**'s API/behaviour | that sibling's `FEEDBACK.md` |

> **Routing migration note.** Historically, cross-cutting / standards-level
> papers went to `mgf-common/FEEDBACK.md`. With the standards extracted to
> this repo, standards / contract / registry papers now belong **here**.
> `FEDERATION.md §7` and the `mgf-fed agents` briefing are updated to
> match.

## Entry template

```
### PAPER-NN — <title>
- **Severity:** 🔴 Blocker | 🟡 Sharp edge | 🟢 Nice-to-have | 💭 Idea
- **Filed by:** <project / who> on <YYYY-MM-DD>
- **What hurts:** <the concrete friction>
- **Why it matters:** <impact / who it blocks>
- **Evidence:** <rule ID, file:line, commit SHA, version hit>
- **Suggested shape:** <what a fix might look like>
- **Status:** OPEN
```

`PAPER-NN` is a monotonic ID, per this repo.

---

## Open

> These five papers came out of a 2026-06-06 red-team of the federation-sync
> mechanism (simulating a sibling-repo agent handed only "run `mgf-common
> sync-prompt` and follow it") against three dimensions: (1) full resource
> access — always the latest, no guesswork; (2) full understanding + value
> capture — lessons + feedback never lost; (3) a recorded, resumable, multi-
> session plan. The **recipe-level** surface of each shipped the same day
> (federation-sync-prompt.md + the new lesson-learned template); the **binding
> rule / mgf-common CLI** parts remain OPEN below and are tracked in
> `pending-feedback/mgf-common.md` where they cross into mgf-common.

### PAPER-03 — `LESSON_LEARNED.md` as a first-class (eventually binding) artifact
- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (red-team session) on 2026-06-06
- **What hurts:** The federation has no home for "the non-obvious thing I
  learned working in THIS repo that the next agent would otherwise rediscover."
  CHANGELOG records what shipped, FEEDBACK routes friction to *another* repo, a
  conformance ledger records rule state — none capture a local lesson. A core
  mgf value (never waste or duplicate effort) had no artifact behind it.
- **Why it matters:** Lessons live only in chat transcripts and evaporate; the
  same gotcha (e.g. the `VIRTUAL_ENV`/uv footgun) gets re-hit across sessions
  and repos.
- **Evidence:** `grep -r LESSON_LEARNED docs/` returns only prose
  (API_DESIGN.md:104); it is absent from the DOCUMENTATION.md required-docs set.
- **Suggested shape:** (a) ✅ SHIPPED recipe surface — new
  `docs/recipes/lesson-learned-md-template.md` + the sync prompt's PHASE-B
  LESSON-OUT step appends a dated entry at the repo root. (b) OPEN — promote to
  a binding **DOC-rule** in DOCUMENTATION.md (append a new id; update README
  Index of MUSTs + the FEDERATION required-docs list, DOC-18 ripple) and gate
  its presence in `mgf-fed catch-up`/`app-doctor --standards` (the CLI change is
  in `pending-feedback/mgf-common.md`). Stage it advisory-now / binding-later so
  the corpus isn't retro-failed before repos create the file.
- **Status:** ✅ SHIPPED as a SHOULD — **DOC-19** (v2.20) drafted + the §2.0.1 matrix / FEDERATION.md / What's-new ripple done (0.1.19). Deliberately staged as a SHOULD (kept OUT of the Index of MUSTs so it doesn't retro-fail every repo's ledger). The remaining step is the future **promotion to MUST** (Index of MUSTs + the `doctor --standards` presence gate = mgf-common PAPER-99), to run as a federation-sync wave once the file is broadly adopted.

### PAPER-04 — Durable cross-repo feedback outbox (`pending-feedback/`)
- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (red-team session) on 2026-06-06
- **What hurts:** The sync prompt tells an agent to route cross-repo findings to
  the target's `FEEDBACK.md` but also that the federation is READ-ONLY ("never
  reach in"). Those two collided: the agent could neither write the paper nor
  keep it — findings degraded to a sentence in a final report that nobody
  relays. Value leaked exactly where the prompt claimed to capture it.
- **Why it matters:** A real bug found in a dependency, or a justified challenge
  to a standard, was lost unless a human happened to read the transcript —
  breaking "never lose value" and the "everything is up for evolution" value.
- **Evidence:** federation-sync-prompt.md GUARDRAILS (pre-fix) "routed to the
  right FEEDBACK.md — never reach in", with no writable destination.
- **Suggested shape:** ✅ SHIPPED recipe surface — a `pending-feedback/<target>.md`
  **outbox in the agent's OWN repo** (the one place it may write): the agent
  drafts a proper `feedback-md-template` paper there, the supervisor relays it
  into the target's `FEEDBACK.md`. OPEN — a short supervisor-side **relay
  recipe** (drain `pending-feedback/` → targets' FEEDBACK via `tea`/PR) and a
  decision on whether `pending-feedback/` presence/format is a binding
  convention or stays recipe-level.
- **Status:** OPEN (mechanism shipped; relay recipe + binding decision pending)

### PAPER-05 — "Latest" is ambiguous; sync can silently bind to a stale pin
- **Severity:** 🔴 Blocker (correctness of the core guarantee)
- **Filed by:** mgf-standard (red-team session) on 2026-06-06
- **What hurts:** The seams resolve the corpus from the version THIS project's
  pin RESOLVES TO — which may lag the latest release. Nothing asserted
  installed == latest, so an agent behind a stale ceiling (`mgf-common<0.47`
  while 0.47 is published) syncs to OLD standards and reports success. "I
  synced" silently meant "to whatever my stale pin resolved," not "to current."
- **Why it matters:** Directly undermines dimension 1 (always get the latest):
  a confident, green, WRONG sync.
- **Evidence:** prompt resolved "from the version THIS project pins" with no
  registry-vs-installed comparison; the live `catch-up` latest-check is an
  unwired TODO (project-state-handoff item #4).
- **Suggested shape:** ✅ SHIPPED prompt surface — STEP 0 **ASSERT-LATEST**:
  compare installed vs the Codeberg simple/REST index, and if the pin ceiling
  excludes the latest, record a REQUIRED pin-walk + FLAG rather than treat
  stale-as-current. OPEN — wire a real live-latest check into `mgf-common
  catch-up` (registry HEAD vs installed snapshot), routed in
  `pending-feedback/mgf-common.md`.
- **Status:** OPEN — prompt guard shipped (0.1.18); CLI side RELAYED + SHIPPED as mgf-common PAPER-98 (no-network snapshot-provenance advisory; catch-up stays offline, live check stays agent-side; PR #9, commit `100b52e`)

### PAPER-06 — No ASK gate: the prompt over-weights autonomy on ambiguous calls
- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-standard (red-team session) on 2026-06-06
- **What hurts:** PHASE A ended "proceed autonomously" with no boundary for
  when to STOP and verify with the human. An agent could unilaterally mark a
  rule N/A, or apply an irreversible/cross-cutting fix, on a genuine judgment
  call — a silent conformance hole or an unsafe change with no checkpoint.
- **Why it matters:** Dimension 3 explicitly wants questions/verification when
  needed; wrong unilateral calls are the costly failure mode.
- **Evidence:** federation-sync-prompt.md (pre-fix) line "proceed autonomously"
  with no ask condition anywhere in PHASE B.
- **Suggested shape:** ✅ SHIPPED — a PHASE-B **ASK gate**: decide autonomously
  when clear, but stop and ask (recording the question in the plan's OPEN
  QUESTIONS first) on ambiguous applicability, irreversible/cross-cutting fixes,
  or rule conflicts. OPEN — decide whether to also encode it as a general
  agent-behaviour MUST in AGENTS.md.
- **Status:** OPEN (shipped in prompt; AGENTS.md encoding decision pending)

### PAPER-07 — Plan ≠ ledger; no multi-session resume protocol
- **Severity:** 🟢 Nice-to-have
- **Filed by:** mgf-standard (red-team session) on 2026-06-06
- **What hurts:** The conformance ledger (state) doubled as the plan (sequence),
  with no sequenced steps, blockers, or open-questions section, and no
  instruction for resuming across sessions — a fresh session might redo PHASE A
  or, worse, assume the work was done.
- **Why it matters:** Big out-of-sync projects need multi-session work;
  dimension 3 wants the plan recorded and resumable.
- **Evidence:** prompt had only ⏳ ledger rows; `grep -i resume|session`
  returned no resume protocol.
- **Suggested shape:** ✅ SHIPPED — PHASE-A **A4 PLAN**
  (`docs/inprogress/<sync>-plan.md`, separate from the ledger, with ordered
  fixes + blockers + OPEN QUESTIONS) and an explicit **RESUME** step (re-run
  STEP 0 + ASSERT-LATEST; if corpus unchanged, continue ⏳ items). OPEN — decide
  whether the `<sync>-plan.md` location is a named convention worth a one-line
  rule.
- **Status:** OPEN (shipped in prompt; naming-convention decision pending)

## Closed

### PAPER-02 — Umbrella-drain slices need a "verify the neutral surface" gate before declaring cross-repo
- **Severity:** 🟡 Sharp edge
- **Filed by:** vm-vmanager-app (Claude Code session) on 2026-06-04
- **What hurts:** When draining a consumer off a retiring umbrella, it is easy
  to MISCLASSIFY a slice. Two failure modes, both hit during the
  vm-vmanager-app providers drain:
  1. *False cross-repo:* `security_facts` was deferred as a 3-repo campaign
     (assumed it needed `domain_pid`/`libvirt_uri`/`network_xml` added across
     core/sdk/libvirt). It was fully **app-only**: `libvirt_uri` was already in
     app config; network isolation was already on `NetworkProfile.forward_mode`
     (`None` ⇒ isolated); and `domain_pid` was **never implemented on the real
     provider**, so the PID-derived facts were dormant no-ops — nothing to
     port. (PR #35.)
  2. *False app-only:* `storage` reads and `vm snap info` LOOK like a clean
     client repoint but are genuinely blocked by **thin observation contracts
     missing fields** — `PoolInfo` lacks `available_bytes`/`active`,
     `_SnapshotInfoContract` lacks `state`. Repointing naively would silently
     drop data.
- **Why it matters:** Misclassification either burns a cross-repo campaign on
  app-only work, or ships a data-losing slice against a too-thin contract.
  Recurs on every umbrella-consumer drain across the federation.
- **Evidence:** vm-vmanager-app PR #35 (security_facts, mapped cross-repo →
  shipped app-only); storage/snapshot-info deferred for real contract gaps.
- **Suggested shape:** Add a "drain slice classification" gate to the
  federation drain recipe. For each provider/umbrella call a slice removes,
  confirm BOTH: (a) the neutral SDK/contract value type actually carries every
  field the consumer *reads* (a same-named method is not enough), and (b)
  whether the legacy implementation even populates it in production — a
  never-implemented method is a dormant no-op, not a feature to preserve. Only
  after both checks fail may a slice be declared cross-repo-blocked.
- **Resolution:** Recipe shipped — [`docs/recipes/umbrella-drain.md`](docs/recipes/umbrella-drain.md):
  the **slice classification gate** is its centerpiece. Per removed
  provider/umbrella call, answer **Q-A** (does the neutral SDK/contract value
  type carry every field the consumer *reads* — fields, not a same-named
  method?) and **Q-B** (does the legacy actually *populate* it in production,
  or is it a dormant no-op?). Decision table: neutral type carries everything →
  app-only; missing field but legacy never populated it → app-only (drop the
  no-op); missing field that legacy genuinely produced → cross-repo-blocked
  (file the field gap to the sibling's FEEDBACK, name the field). Both
  vm-vmanager-app failure modes (false-cross-repo `security_facts`,
  false-app-only `PoolInfo`/`_SnapshotInfoContract`) are worked into it.
  Indexed in `docs/recipes/README.md`; cross-linked from `library-app-separation`
  / `codeberg-pr` / `cohort-release-wave`. Closed 2026-06-04.
- **Status:** CLOSED

### PAPER-01 — No documented CLI recipe for agents to open Codeberg PRs
- **Severity:** 🟡 Sharp edge
- **Filed by:** vm-vmanager-app (Claude Code session) on 2026-06-03
- **What hurts:** Landing a slice ends in a Codeberg (Forgejo) PR, but the
  federation had no documented or installed CLI for PR operations. `gh` is
  GitHub-only; `tea` (the Gitea/Forgejo CLI) was not installed. So an agent
  that finishes a green branch can `git push` but cannot open the PR — it can
  only hand back the `…/compare/main...<branch>` URL for a human to click.
- **Why it matters:** Every federation repo lands work as Codeberg PRs (e.g.
  vm-vmanager-app #27–#34). The missing tool wedges a manual step into an
  otherwise fully-automatable land-the-slice loop, across all repos and agents.
- **Evidence:** vm-vmanager-app `drain-providers-snapshot` slice — branch
  pushed, PR opened manually via the compare URL because no `tea`/`gh` was on
  PATH; remote `ssh://git@codeberg.org/magogi-admin/vm-vmanager-app.git`.
- **Suggested shape:** Adopt `tea` as the standard Codeberg CLI in a
  federation-process recipe: install (static binary → `~/.local/bin/tea`, from
  dl.gitea.com), a one-time `tea login add` with a scoped Codeberg token, and
  the canonical `tea pr create --base main --head <branch> --title … --body …`
  invocation (plus `tea pr merge`). Document it beside the existing
  land-the-slice / PR process so any agent can open and merge PRs directly.
- **Resolution:** Recipe shipped — [`docs/recipes/codeberg-pr.md`](docs/recipes/codeberg-pr.md):
  `tea` install, a **repository-scoped** Codeberg login (distinct from the
  `write:package` publish token — a package-only token `403`s on `pr create`),
  and the real flag surface (`tea pr create --base/--head/--title/`**`--description`**
  — note it's `-d`, not `gh`'s `--body`; some repos branch-protect `master`).
  Indexed in `docs/recipes/README.md`; cross-linked from the sync-prompt and
  cohort-release recipes. Merging stays the supervisor's call (WF-01 + release
  discipline). Closed 2026-06-04.
- **Status:** CLOSED

---

## Plasma project feedback cohort — filed 2026-06-11 (PAPER-100..106)

External feedback from the **Plasma project** (Magogi Foundation), submitted as
`docs/inprogress/Plasma_To_Magogi_Feedback_Papers_v0_1.md` (staging ids P-A..P-G; bundle
review of v0.1.48). All seven re-verified against the live corpus on filing. Six shipped
same-day; PAPER-100 (the audit-spine gap) shipped in mgf-standard 0.1.50 — the full 218→258 re-baseline. **All seven now resolved.**

### PAPER-100 — Index of MUSTs omits 5 families + REL-10..13 (~40 binding rules); headline propagates the gap
- **Severity:** 🔴 Blocker
- **Filed by:** the Plasma project on 2026-06-11 (staging id P-A)
- **What hurts:** The *Index of MUSTs* lists 218 rows across 16 families, but the corpus ships 21 binding families. TM/PF/OB/NM/GUI have **zero** index rows and REL-10..13 are defined-yet-unindexed (~40 MUST-level rules). Verified live: index = 218; 0 rows for each of those families; 0 for REL-10..13.
- **Why it matters:** The Index is the audit spine — CFM-03 and `doctor --standards` gate ledger completeness against it. A project passes "complete" against an incomplete denominator (~40 binding rules never audited). The index's own maintenance note shows the manual same-commit discipline has lost this race before.
- **Evidence:** `docs/standards/README.md` Index (218 rows); `TIME.md` / `PERFORMANCE.md` / `OBSERVABILITY.md` (OB-11) / `NAMING.md` / `GUI_CONSUMERS.md` binding families; `RELEASING.md` REL-10..13.
- **Suggested shape:** Generate the Index + headline counts from the per-doc Rules boxes (`gen_rules_index.py` + a `rules.json` companion, `--check` CI staleness gate) — apply REL-13's own medicine to the index. Interim: manual refill of the 5 families + REL-10..13 + the headline, in one DOC-18 commit.
- **Fleet impact (why it was held OPEN):** expanding the audited set (218 → 258) re-baselines **every repo's §Z ledger** — a federation event requiring an explicit go-ahead, not a doc tweak. The go-ahead was given; executed in full this session.
- **Resolution:** SHIPPED (`mgf-standard` **0.1.50**, 2026-06-13) — the suggested shape, end to end, in four phases: (1) Index of MUSTs refilled **218→258** across 21 families; (2) `scripts/gen_rules_index.py --check` anti-drift gate + `rules.json` companion (REL-13's own medicine applied to the index) + `tests/unit/test_rules_index.py`; (3) **all 21 federation §Z ledgers re-baselined to 258** — forward-compatible, since `doctor --standards` reads the Index dynamically via `mgf.standard.read_standard`, so no fleet break until the release; (4) released `mgf-standard 0.1.50` — a fresh install ships `read_standard("README")` = 258 rows, so every doctor enforces 258 on pin-refresh; REL-13 GREEN + CURRENT. Runbook: `docs/archive/2026/PAPER-100-rollout.md`; seed helper `scripts/seed_ledger_rebaseline.py`. (Built on top, same court-intake spirit: **FB-05** + the `mgf-fed feedback` relay tool, closing the outbox-handling seam this review's filing implicitly exercised.)
- **Status:** SHIPPED

### PAPER-101 — README navigation text stale vs the docs it indexes (P-B)
- **Severity:** 🟡 Sharp edge
- **Filed by:** the Plasma project on 2026-06-11 (P-B)
- **What hurts:** The directory tree said OBSERVABILITY "OB-01..OB-08" (ships OB-01..**11**, incl. OB-10 the cross-language wire rule); the reading-order table said RELEASING "REL-01..REL-08" (ships REL-01..**13**). A DOC-18 propagation miss inside the doc that defines DOC-18.
- **Suggested shape:** correct the ranges now; permanently subsumed by PAPER-100's generation.
- **Resolution:** SHIPPED — `docs/standards/README.md` ranges corrected to `OB-01..OB-11` and `REL-01..REL-13`.
- **Status:** SHIPPED

### PAPER-102 — Cookiecutter overlay invites a non-existent conformance level (P-C)
- **Severity:** 🟡 Sharp edge
- **Filed by:** the Plasma project on 2026-06-11 (P-C)
- **What hurts:** `PROJECT_OVERLAY.md` said "Replace `<n>` with `0`, `1`, or `2`" but the ladder is L1/L2/L3 (CFM-02) — the first conformance declaration a scaffolded project makes was off-by-one (invites L0).
- **Suggested shape:** fix to `1`/`2`/`3`; (follow-up) a `doctor --standards` lint that declared level ∈ {L1,L2,L3}.
- **Resolution:** SHIPPED — template line corrected to `1`/`2`/`3`. The validating-lint is noted as a follow-up enhancement.
- **Status:** SHIPPED

### PAPER-103 — Paper-closure status vocabulary drifts between REL-09 and the FEEDBACK template (P-D)
- **Severity:** 🟢 Nice-to-have
- **Filed by:** the Plasma project on 2026-06-11 (P-D)
- **What hurts:** REL-09 said `SHIPPED / DEFERRED / ❌ REJECTED`; the canonical FEEDBACK template's set is `OPEN/SHIPPED/DEFERRED/WONTFIX/MOVED` — two sanctioned vocabularies for one lifecycle field.
- **Suggested shape:** the template's set is canonical (FB-01..04 own the lifecycle); reword REL-09 `REJECTED → WONTFIX`.
- **Resolution:** SHIPPED — REL-09 reworded to `SHIPPED / DEFERRED / WONTFIX`.
- **Status:** SHIPPED

### PAPER-104 — `evidence_level` term drifts between the principle and the recipe (P-E)
- **Severity:** 🟢 Nice-to-have
- **Filed by:** the Plasma project on 2026-06-11 (P-E)
- **What hurts:** `audit-outcomes` defines `VERIFIED_BY_EXECUTION`; `architecture-review` instructed `CONFIRMED_BY_EXECUTION` — synonym drift makes "show me everything execution-verified" a two-pattern query forever.
- **Suggested shape:** the principles doc owns the vocabulary; align the recipe to `VERIFIED_*`.
- **Resolution:** SHIPPED — `docs/recipes/architecture-review.md` aligned to `VERIFIED_BY_EXECUTION` (all occurrences).
- **Status:** SHIPPED

### PAPER-105 — Bundle agent briefing self-identifies as a different version than the bundle provenance (P-F)
- **Severity:** 🟢 Nice-to-have
- **Filed by:** the Plasma project on 2026-06-11 (P-F)
- **What hurts:** the shared `magogi-standard-bundle` said "captured from v0.1.48" while its embedded `AGENT_BRIEFING.md` said "you are here: mgf-standard 0.1.46" — OP-04 (headline-facts) applied to the export artifact.
- **Suggested shape:** the export regenerates + stamps both files from one source.
- **Resolution:** SHIPPED — the bundle briefing stamped to `0.1.48` to match the capture. **Durable fix recorded:** any future bundle export MUST stamp the briefing version from the same source as the capture provenance (one source of truth).
- **Status:** SHIPPED

### PAPER-106 — Raw NUL byte in TESTING.md flips the largest rules doc to "binary" (P-G)
- **Severity:** 🟡 Sharp edge (VERIFIED_BY_EXECUTION)
- **Filed by:** the Plasma project on 2026-06-11 (P-G)
- **What hurts:** `TESTING.md` held one literal NUL (offset 26561, line 355) inside a Hypothesis example meant to read `@example(value="\x00")` — the raw byte was embedded instead of the escape text. The file decoded as valid UTF-8 (hid from encoding checks) but `file(1)` reported `data` and GNU grep's binary heuristic suppressed matches — a daily DOC-12 (AI-navigability) cost on the doc holding TS-01..28.
- **Suggested shape:** replace the byte with the literal `\x00`; add a CI gate rejecting control bytes outside `\t\n\r` over `docs/standards/`.
- **Resolution:** SHIPPED — NUL replaced with the literal text `\x00` (example now renders as intended; `file(1)` → UTF-8 text). Prevention gate added: `tests/unit/test_no_control_bytes.py` scans the corpus for control bytes (catches the class in CI).
- **Status:** SHIPPED

---

### PAPER-107 — No shared-code / duplication discipline; a mature adopter independently built one
- **Severity:** 🟡 Sharp edge
- **Filed by:** Claude Code session (Magogi intake review of the Plasma "Commons" standard) on 2026-06-11
- **What hurts:** The corpus has SP (scope) + DP-08 (replaceability swap-recipes) but **no design-time discipline against code duplication / for shared-code admission**. There is no rule-of-three, no DUP-defect class, no junk-drawer-module-name ban, no blame-free-eviction stance, and no "duplication is cheaper than the wrong abstraction" counterweight. Verified: grep of `docs/standards/` finds **0** hits for duplication-as-defect, commons/shared-code, junk-drawer naming, rule-of-three, or eviction; NAMING.md has no `utils/misc/helpers` ban.
- **Why it matters:** "Silent divergence — two implementations of one rule drifting until they disagree" is a real consistency risk in a federation of ~22 libraries that **share** concepts (canonical encoders, ID schemes, time, hashing) through mgf-common. Without a design-time check, duplication accretes; with over-eager extraction, a god-library forms — both defects the corpus currently names neither. The Plasma project — a sophisticated adopter that took our principles trio nearly verbatim — independently authored a full "Shared Code Standard (the Commons)" to fill exactly this gap. An adopter reinventing a discipline is strong signal the gap is real.
- **Evidence:** reference design — `soothfast/ops/Plasma_Shared_Code_Standard_v0_2.md` (Commons Check §1, R1 rule-of-three §2, DUP defects §5, blame-free eviction §4, blast-radius tiers §3, failure modes §8). Our gap — `docs/standards/` grep = 0; DP-08 covers only swap-recipes (`DESIGN_PRINCIPLES.md:407`).
- **Suggested shape:** a short new discipline (a DP SHOULD-cluster or `docs/recipes/shared-code.md`) folding back the field-proven elements: (1) a design-time **Commons/DUP check** before adding a module ("does an existing module own this behavior?"); (2) a **rule-of-three** extraction trigger (two confirmed consumers OR a third occurrence — speculation doesn't count); (3) **duplication-as-defect** with cheap waivers (incidental dup needs none — judgment over bureaucracy); (4) a **junk-drawer-name ban** (no `utils/misc/helpers/common` — domain names only), likely a NAMING (NM) rule; (5) **blame-free eviction** as first-class (reversing a wrong abstraction is a success, not a failure); (6) the **"duplication is cheaper than the wrong abstraction"** counterweight stated up front so the discipline can't be weaponised into premature abstraction; (7) a **blast-radius tier** on shared code (output bytes that reach the wire/contract = a contract tier — ties to OB-10 + AP stability). Credit the Plasma Commons standard as the reference.
- **Resolution:** SHIPPED — authored as `docs/recipes/shared-code.md` (the full discipline, federation-adapted) + **NM-05** (junk-drawer-name SHOULD); indexed in `docs/recipes/README.md`; cross-referenced from NAMING. Kept recipe/SHOULD-level (not a new MUST) so it binds the fleet without reopening every §Z ledger (the PAPER-100 blast radius) — promotable to a MUST if/when the Index re-baseline lands. Fleet scan: no junk-drawer modules present (already compliant). Added to the cookiecutter for new projects.
- **Status:** SHIPPED

### PAPER-108 — Federation Zig CI template: pin not bound to the lock, no retry-wrap, no validator lane, no `verify_ci` checker
- **Severity:** 🟡 Sharp edge (two of the four are silent reliability/determinism hazards)
- **Filed by:** Claude Code session (sooth-family CI hardening sweep, relayed via sooth-standard) on 2026-06-30
- **What hurts:** Four gaps in the root Zig CI template + the CI tooling, each inherited by everything derived from it:
  1. **Pin not bound to the lock.** `templates/woodpecker/base-zig.yml` pins `ziglang==0.16.0` as a bare literal, not tied to a committed `toolchain.lock`. CI can build on a different compiler than the lock pins and nothing notices — a determinism break (the PRV-10 "pins bound to ground truth" idea, unenforced for CI).
  2. **No retry-wrap.** The `pip install ziglang` is not retry-wrapped, so a transient PyPI/network blip reds the pipeline. sooth hit this exact flake — a doc-only push reded while the *identical* step passed on the PR check.
  3. **No bundle-validator lane.** The gate runs build/test/fmt but never the repo's bundle validator (the "definition of done").
  4. **No `verify_ci` checker.** mgf-standard ships no `tools/verify_ci.py`; there is no machine check that a federation repo's `.woodpecker.yml` conforms (full-clone, secret-scan, triggers, the strict-YAML quoted-colon footgun, pin↔lock, validator-in-CI). Conformance is by eye.
- **Why it matters:** `base-zig.yml` is the root template the whole federation (~22 libs) and the sooth family derive from — every gap propagates fleet-wide. (1)+(2) are silent reliability/determinism hazards; (4) leaves CI conformance unenforced. Related federation hazard: `base-zig.yml` **and** soothai pin Zig **0.16.0**, but the sooth kernel (`soothfast`) and every path-dep sibling pin **0.15.1** — sharing Zig across that boundary is a determinism break, and soothai's own `toolchain.lock` already flags the split as needing reconciliation.
- **Evidence:** `mgf-standard/templates/woodpecker/base-zig.yml:30,37` (bare `ziglang==0.16.0`; no `--retries`/`--timeout`/`||`-retry); absent `mgf-standard/tools/verify_ci.py`. Field-proven reference (sooth, offered for promotion as-is): `sooth-standard/tools/verify_ci.py` (the checker incl. the new required pin↔lock check + the validator-in-CI advisory), `sooth-standard/templates/woodpecker/zig.yml` (retry-wrap + pin-from-lock + a `validate-bundle` lane), `sooth-standard/docs/recipes/ci-onboarding.md`, commit `8565641`.
- **Suggested shape:** fold the sooth hardening back into `base-zig.yml` + mgf CI tooling: (a) retry-wrap the toolchain fetch (`pip install --retries 8 --timeout 60 … || (sleep 10; retry)`); (b) require `ziglang==X.Y.Z` to equal a committed `toolchain.lock`, enforced by a `verify_ci`-style required check; (c) add an opt-in bundle-validator lane; (d) adopt `verify_ci.py` federation-wide (stdlib, no network → usable as a CI step itself); (e) declare a coherent federation Zig line (or a documented policy for the 0.16/0.15 split). Credit the sooth-standard CI work as the reference; `verify_ci.py` is offered for direct promotion.
- **Resolution:** SHIPPED — branch `feedback/ship-paper-108-113-114` (EXTEND per LOCK §2/§4-1: the status becomes true at the founder's enacting merge; the WF-13 fleet re-render wave follows it). Per gap: (a) retry-wrap had already landed pre-lock (`base-zig.yml` toolchain fetches). (b) pin↔lock: sooth `verify_ci` check-#10 **semantics folded into the template itself** — an inline coherence gate in `base-zig.yml`'s `zig-build-and-test` (reads the pin from the rendered pipeline, no duplicated literal; committed `toolchain.lock` + mismatch = red, no lock = noted-not-fatal — no mgf Zig repo ships a lock yet, so the gate arms per-repo as locks land). (c) validator lane ships as a commented opt-in block in `base-zig.yml` (no mgf Zig repo has a bundle validator today; enabling is a WF-13 template/generator event, never a hand-edit of a rendered pipeline). (d) `tools/verify_ci.py` adoption **deliberately deferred**: the Court-blessed disposition (LOCK §4-1) is an *EXTEND-class template change*, and both REQUIRED semantics the checker would add (#9 branch-filter — via PAPER-114 — and #10 pin↔lock) now execute in the pipeline itself; promoting the stdlib checker as an mgf tool + gate is a clean follow-up if wanted. (e) the 0.16/0.15 federation-Zig-line split stays a founder policy call — the inline gate makes any per-repo split explicit rather than silent. The push branch-filter (gap-1's sibling hazard) shipped under PAPER-114 in the same branch.
- **Status:** SHIPPED

### PAPER-109 — Base `clone` step flakes on auth ("could not read Username"); fleet pays a manual-re-trigger tax
- **Severity:** 🟡 Sharp edge
- **Filed by:** Claude Code session (sooth CI + federation sweep) on 2026-07-01
- **What hurts:** The base `clone` step intermittently fails with `fatal: could not read Username for 'https://codeberg.org': No such device or address` (exit 128) — the clone gets **no credentials** for the private repo, so every downstream step is skipped and the pipeline reds through no fault of the change. This is distinct from WF-09 (the treeless-partial-clone 504 that `partial: false` already fixes) — it is a clone-**auth/credential-injection** flake. The fleet's current mitigation is a **manual re-trigger**.
- **Why it matters:** it reds PRs spuriously, costs a human a manual re-trigger each time, and — worse — erodes trust in "red" (people learn to shrug off reds, so a real failure eventually slips through). It hits Python and Zig repos alike (it's the base clone, not the toolchain).
- **Evidence:** in one sweep, **5 sooth PRs** reded on the `clone` step (soothfast/soothweave/first-light/sooth-brand + a re-run of mgf-standard); e.g. soothfast pipeline #6 clone log: `git fetch … +be80406…: → fatal: could not read Username for 'https://codeberg.org': No such device or address; exit status 128`. **All cleared on a plain restart** (soothfast #7, soothweave #4, first-light #5, sooth-brand #4, mgf-standard #193 — all success), confirming it's transient. Correlated with a Woodpecker agent wedge/restart window (see PAPER-111). The fleet already re-triggers for this by hand — `mgf-fed` pipeline **#154** message: *"ci: re-trigger pipeline (clone step flaked)."*
- **Suggested shape:** (a) wrap the base clone in a **bounded retry** (the git-plugin supports retries, or a retry step) — the same discipline already blessed for the toolchain fetch; (b) OR auto-retrigger on a *clone-only* failure; (c) root-cause the credential gap (does the repo netrc drop on `pull_request` events or during an agent reconnect?); (d) at minimum, document in `run-ci.md` that a clone-only red is a known-transient, so it isn't re-investigated from scratch each time.
- **Status:** OPEN

### PAPER-110 — Automated fleet orchestration exhausts Codeberg's API rate limit (429), then can't merge for ~an hour
- **Severity:** 🟡 Sharp edge
- **Filed by:** Claude Code session on 2026-07-01
- **What hurts:** Driving the fleet through the **Codeberg REST API** at automation speed — polling commit-status, opening PRs, restarting pipelines, merging — exhausts Codeberg's shared fair-use quota and returns **HTTP 429 Too Many Requests**. The 429 then **blocks merges** (`POST /pulls/{i}/merge` 429s) for what looks like an hourly window, so an agent can wedge itself out of the very merges it was driving. `activate-ci.md` warns rollout is "paced," but there's no concrete **API-budget** guidance, so an agent naturally polls aggressively and trips it. `mgf-fed` (which orchestrates exactly this) is the prime exposure.
- **Why it matters:** it turns a finished, all-green batch into a "can't land for an hour" stall, and retries compound the window. It silently caps how fast the federation can move PRs/releases, and it's invisible until you hit the wall.
- **Evidence:** after opening 9 PRs + polling 8 commit-statuses every ~20s + restarting 5 pipelines, **every** merge `POST` returned `429` even when spaced 25s apart; `GET /pulls/{i}` returned the 429 HTML page. Recovered only by **stopping all Codeberg calls**. (Contrast: Woodpecker — self-hosted — was never rate-limited, so status polling should go there.)
- **Suggested shape:** bake a **Codeberg API rate-budget** into the recipe + `mgf-fed`: (a) poll **Woodpecker** (self-hosted, unmetered) for pipeline status in loops, **never** Codeberg commit-status; (b) cap Codeberg API calls (e.g. ≤N/min) with **exponential backoff on 429**; (c) space PR opens/merges generously and batch them; (d) prefer **SSH git** (unmetered) over the HTTP API where possible (push, not `tea` where avoidable); (e) document the observed limit (hourly bucket) in `activate-ci.md` beside the existing "fair-use" note.
- **Status:** OPEN

### PAPER-111 — The `*/5` agent self-heal cron did not clear a 15-min wedge (silent-failing safety net)
- **Severity:** 🟡 Sharp edge
- **Filed by:** Claude Code session on 2026-07-01
- **What hurts:** `MGF_INFRA.md §2` documents that a `*/5` self-heal cron restarts a wedged Woodpecker agent (`worker_count=0`). But an observed wedge **persisted 15+ minutes** — the whole fleet's CI stalled (queue `worker_count:0, pending:13, running:4`) — and cleared only after a **manual** `sudo docker compose restart woodpecker-agent`. So the documented auto-recovery either isn't running, doesn't detect this wedge shape (workers=0 with a backlog, vs container-down), or restarts-but-re-wedges.
- **Why it matters:** the self-heal cron is the safety net for the single most critical shared resource — the CI runner. If it silently doesn't fire, one wedge takes the **entire fleet's CI** down until a human notices and SSHes in. It also defeats the AG-20 promise (owned infra self-recovers) precisely where that promise is automated.
- **Evidence:** queue `{worker_count:0, pending_count:13, running_count:4}` sustained 15+ min (all 9 pipelines stuck `pending`); the manual `docker compose restart woodpecker-agent` (MGF_INFRA §2) immediately drained it (pending 13→6→0, worker_count→4, two agents cap 2 each). No sign the `*/5` cron acted in that window.
- **Suggested shape:** verify the self-heal cron exists and runs (inspect its crontab + logs); make its trigger the **wedge condition** (`worker_count==0 && pending>0`), not just container-down; and **emit a signal** (OpenObserve record / push) whenever it fires *or* when a wedge persists > N minutes — a silent-failing self-heal must itself be observable. Consider a compose `healthcheck` that restarts the agent on failed heartbeat.
- **Status:** OPEN

### PAPER-112 — Standards corpus still describes Codeberg-PyPI publishing after the devpi cutover
- **Severity:** 🟢 Nice-to-have (documentation-vs-running-system coherence; the cutover itself is intentional)
- **Filed by:** Claude Code session (federation CI/remote review) on 2026-07-01
- **What hurts:** The fleet is mid-cutover from the Codeberg PyPI registry to the self-hosted **devpi** index, and the two sources of truth disagree on the load-bearing "where do releases go?" question. The **running system** publishes to devpi: the generated `templates/woodpecker/base.yml` publish step targets `195.15.203.250/magogi/prod/` with `TWINE_USERNAME: magogi` / `TWINE_PASSWORD: {from_secret: devpi_password}`; `templates/policy.toml` lists `verify-devpi` as a required step; `frag-no-pypi-surface.yml` (PKG-12) guards against `pypi.org`; `MGF_INFRA.md` names devpi as "the private PyPI the fleet publishes to + resolves from." But large parts of the **standards corpus** still describe the Codeberg path: `docs/standards/WORKFLOWS.md` (WF-03/04/08 — `codeberg_package_token`), `PACKAGE_DISTRIBUTION.md` (PKG-*), `docs/standards/AGENTS.md` (AG-09), and `docs/recipes/cohort-release-wave.md`.
- **Why it matters:** an agent (or human) reasoning about the *current* publish flow from the corpus reaches the **wrong** (Codeberg) answer — exactly the silent running-system-vs-written-standard divergence the federation guards against (it's the sibling of PAPER-05's "stale-as-current"). It's low-severity because the cutover is deliberate and the generated CI is already correct — but the corpus is what agents read.
- **Evidence:** running truth — `templates/woodpecker/base.yml` (publish → devpi), `templates/policy.toml` (`verify-devpi` required), `frag-no-pypi-surface.yml` (PKG-12), `MGF_INFRA.md §1/§2` (devpi + `devpi_password`). Lagging corpus — `WORKFLOWS.md` WF-03/04/08, `PACKAGE_DISTRIBUTION.md` PKG-*, `AGENTS.md` AG-09, `docs/recipes/cohort-release-wave.md`. Cross-checked in `docs/memory/release-and-registry.md` (which already flags devpi as current).
- **Suggested shape:** a reconciliation sweep of those docs to the devpi truth (retarget WF-03/04/08 + PKG-* + AG-09 + `cohort-release-wave.md`, swap `codeberg_package_token` → `devpi_password`). Until that lands, a one-line banner at the top of each lagging doc — *"CUTOVER IN PROGRESS (Phase-2 B2): devpi is the current publish target; Codeberg-PyPI references below are pre-cutover"* — so a reader knows which source is authoritative.
- **Status:** ACCEPTED — (1) the interim banner shipped in PR #179 (2026-07-01); (2) the full reconciliation sweep is in PR `feedback/devpi-retarget` — the whole publish corpus (`PACKAGE_DISTRIBUTION.md` PKG-01..12, `WORKFLOWS.md` WF-03/04/06/08, `RELEASING.md` REL-04/05/06, `WORKFLOWS_PATTERNS.md` §4, `AGENTS.md` AG-09/AG-16/AG-18, `NAMING.md`, `COMMON_COMPONENTS.md`, the README *Index of MUSTs*, the recipes + conformance ledger) retargeted to the **verified live** devpi model, interim banners removed. Flips to SHIPPED when that PR merges. Three host-ops decisions are flagged inline for confirmation (§4 identity split, §8 recovery procedure, §6 explicit stage-pinning); the dead `scripts/verify_codeberg.py` is a candidate for a follow-up removal.

### PAPER-113 — Generated Woodpecker templates inline the devpi index endpoint (3× in `base.yml` + 3 fragments) instead of one generated `DEVPI_URL`/anchor — an IP move is a fleet-wide committed-pipeline edit
- **Severity:** 🟡 Sharp edge
- **Filed by:** sooth family — cross-family reuse review on 2026-07-01
- **What hurts:** `templates/woodpecker/base.yml` is a **generated** artifact (header lines 1–3: "GENERATED — do not hand-edit … rendered by `mgf-fed` (mgf.fed._woodpecker) … never patch this file in place"; WF-13), yet it inlines the full devpi endpoint `https://195.15.203.250.sslip.io/magogi/prod/` as a bare literal in **3** separate steps — `--index-url` (`:120`), `--repository-url` (`:239`), and a `url=` string (`:254`). The same host is inlined again in three fragments (`frag-cornerstone-ping.yml`, `frag-fedres-check.yml`, `frag-mgf-common-matrix.yml`) and the cookiecutter, and **nothing** references a single `DEVPI_URL` env / YAML anchor (a `grep` for one finds none). Because the generator renders this text into **every** consumer repo's committed `.woodpecker.yml`, the endpoint is copied fleet-wide. `195.15.203.250.sslip.io` is *not* a stable indirection — sslip.io wildcard-DNS encodes the IP in the hostname (`<ip>.sslip.io` → that IP), so the "hostname" moves with the IP.
- **Why it matters:** A generated file is exactly where an endpoint should be emitted **once** and referenced. Today a devpi host-IP change — which `MGF_INFRA.md §4` explicitly names as a maintenance trigger ("Update when … a host IP changes") — is not a one-line generator edit; it is an N-repos × 3-sites rewrite of committed pipelines, plus the three fragments and the cookiecutter, with every repo then regenerating + re-committing. `MGF_INFRA.md:31` is the declared single source of truth for that endpoint (URL + raw IP), but the generated CI doesn't honor it — it copies the literal. This is the same source-of-truth-vs-N-copies drift shape as PAPER-112, one layer down.
- **Evidence:** `templates/woodpecker/base.yml:120` (`--index-url`), `:239` (`--repository-url`), `:254` (`url=`) — `grep -c '195\.15\.203\.250' base.yml` = **3**. Same host inlined at `frag-cornerstone-ping.yml:21`, `frag-fedres-check.yml:12` (×2, retry), `frag-mgf-common-matrix.yml:21` (×2, retry), + `templates/cookiecutter-mgf-project/.../{pyproject.toml,README.md}`; **0** uses of a `DEVPI_URL`/`INDEX_URL` env or YAML anchor. Generated-artifact provenance: `base.yml:1–3`; WF-13 at `MGF_INFRA.md:72` + `docs/recipes/run-ci.md:18,25`. Endpoint as single-source-of-truth + IP-can-move: `MGF_INFRA.md:31` (endpoint = URL + raw IP `195.15.203.250`), `MGF_INFRA.md:67` ("a host IP changes"). *(Note: mgf-standard has no verbatim "never hardcode an IP" rule; this paper rests on the MGF_INFRA single-source-of-truth + the maintenance-trigger, not an asserted rule.)*
- **Suggested shape:** Have `mgf.fed._woodpecker` emit the index endpoint **once** — a top-of-pipeline `DEVPI_URL` env (or a YAML anchor) sourced from a single generator constant that itself derives from `MGF_INFRA.md`/`policy.toml` — and reference `$DEVPI_URL` at the three call sites (and the fragments/cookiecutter). Then a host move is one generator-constant change + a regenerate wave, not a hand-search across every committed pipeline. Pairs naturally with PAPER-112's reconciliation sweep (one endpoint, one authority).
- **Resolution:** SHIPPED — branch `feedback/ship-paper-108-113-114` (EXTEND; true at the founder's enacting merge; fleet re-render wave follows, WF-13). Landed **stronger than the ask's env-emission**: the endpoint is not emitted per-pipeline at all — every template/fragment site references `$DEVPI_URL`, a Woodpecker **server-level** variable (`WOODPECKER_ENVIRONMENT` on the CI host, live since 2026-07-04; value = the `MGF_INFRA.md §1` endpoint `…/magogi/prod/+simple/`; name coordinated with sooth-standard, whose conversion is already live-green), each consuming step led by a fail-fast guard line. Converted sites: `base.yml` ×3 (`app-doctor --index-url "$DEVPI_URL"`; publish `--repository-url "${DEVPI_URL%%+simple*}"`; verify-devpi `url = '${DEVPI_URL%/}/…'`), `frag-cornerstone.yml` ×2, `frag-fedres-check.yml` ×2, `frag-mgf-common-matrix.yml` ×2, `frag-cornerstone-ping.yml` ×1 — `grep -c '195\.15\.203\.250' templates/woodpecker/` = **0**. `MGF_INFRA.md §2` documents the variable + the host-move procedure. **Consciously left for companions:** the *generator-side* installs (mgf-fed's `_DEVPI_INDEX` constant behind `{{EXTRAS_INSTALL_CMD}}`/`{{SMOKE_INSTALL}}` etc.) still inline the literal — that is `mgf.fed._woodpecker` code, a cross-repo companion change per the paper's own suggested shape; and the cookiecutter `pyproject.toml`/`README.md`, which are committed files a shell variable cannot reach (their endpoint follows PKG-*/PAPER-112 docs truth). A host move is now: server variable + `MGF_INFRA §1` row + mgf-fed constant + re-render wave.
- **Status:** SHIPPED

### PAPER-114 — `base-zig.yml` missing two sooth-proven fixes: `push` not branch-filtered (exit-126 PR/push contention) + no CI-pin↔`toolchain.lock` coherence check
- **Severity:** 🟡 Sharp edge
- **Filed by:** sooth family — cross-family reuse review on 2026-07-01
- **What hurts:** The root Zig CI template `templates/woodpecker/base-zig.yml` — which every mgf Zig repo derives from — is missing two fixes the sooth family already proved out and now ships:
  1. **`push` is not branch-filtered.** The `when:` block triggers on `push` *and* `pull_request` with no `branch:` filter on `push` (`base-zig.yml:10–13`), so on a feature-branch PR both pipelines run **concurrently on the same commit**. The redundant push run contends with the PR run over the shared workspace / container exec; sooth hit exactly this as an intermittent **exit 126**. (The Python `base.yml` shares the unfiltered shape, but the Zig template's `zig build` / container-exec steps are where the contention actually bit.)
  2. **No CI-pin↔lock coherence check.** `base-zig.yml:33,47` pin a bare `ziglang==0.16.0` with no coupling to a committed `toolchain.lock`, and mgf-standard ships no `tools/verify_ci.py` — so nothing verifies the pipeline's compiler equals the repo's pinned toolchain. CI can silently build on a different compiler than the lock pins (a determinism break).
- **Why it matters:** `base-zig.yml` is the shared root for mgf's own Zig libraries (e.g. mgf-hrb); both gaps propagate to each. (1) reds PRs spuriously and trains people to shrug off "red" (the sibling failure mode named in PAPER-109). (2) is a silent determinism hazard — the "pin bound to ground truth" idea, unenforced for CI. Both are field-proven fixes the sooth family is offering back.
- **Evidence:** Gap 1 — `base-zig.yml:11` (`- event: push`, no following `branch:`). Sooth fix: `sooth-standard/templates/woodpecker/zig.yml:14` (`branch: main` under `- event: push`), rationale `zig.yml:7–10` + `sooth-standard/docs/recipes/ci-onboarding.md:69–72` ("runs concurrently with the PR pipeline on the *same commit* … the contention can kill a step (a container exec / exit 126)"); enforced by `sooth-standard/tools/verify_ci.py:104–110` (check #9). Gap 2 — `base-zig.yml:33,47` (bare `ziglang==0.16.0`, no lock). Sooth fix: `verify_ci.py:112–125` (check #10), fail branch `:121–123` — `fail("CI ziglang==%s != toolchain.lock %s (%s) -- the CI pin must match the lock (VAL-08)" …)`; the lock is real + populated (`soothfast`/`soothtroth`/`soothweave` `toolchain.lock` pin `"version": "0.15.1"`). **Overlaps PAPER-108** (OPEN): gap 2 restates PAPER-108's gap-1 (pin not bound to lock) + gap-4 (no `verify_ci`) with the now-shipping sooth check; gap 1 (branch filter) is net-new. (PAPER-108's gap-2 retry-wrap has since landed at `base-zig.yml:33,47`.)
- **Suggested shape:** Fold both into `base-zig.yml` + mgf CI tooling: (a) add `branch: <default>` under the `push` event in the Zig template's `when:` block (and the Python `base.yml`); (b) bind the `ziglang==X.Y.Z` pin to a committed `toolchain.lock` and enforce equality via a `verify_ci`-style required check — adopt `sooth-standard/tools/verify_ci.py` (stdlib-only, no network, runnable as a CI step; it already ships both check #9 and check #10). Best merged with PAPER-108's disposition, since they cover the same Zig-template + CI-tooling surface. Credit the sooth-standard CI work as the reference.
- **Resolution:** SHIPPED — branch `feedback/ship-paper-108-113-114`, merged with PAPER-108's disposition as the paper suggested (EXTEND; true at the founder's enacting merge; fleet re-render wave follows, WF-13). Gap 1: `branch: main` now filters the `push` event in **both** `templates/woodpecker/base-zig.yml` and the Python `base.yml` `when:` blocks, with the sooth rationale comment (exit-126 same-commit contention; lineage `sooth-standard/templates/woodpecker/zig.yml:14` + `ci-onboarding.md:69-72`) — tag/cron/manual events untouched, and every fleet repo's default branch is `main` (verified; a different default becomes a generator param, not a hand-edit). Gap 2: the CI-pin↔`toolchain.lock` coherence check runs **inline in `base-zig.yml`** (check-#10 semantics: mismatch = red, no committed lock = noted; the pin is read from the rendered pipeline so no literal is duplicated). `verify_ci.py` itself deferred with reasons under PAPER-108's resolution. Credit: the sooth-standard CI hardening is the reference for both fixes.
- **Status:** SHIPPED

### PAPER-115 — Canonical CI lint step enforces `ruff check` but not `ruff format --check` — format drifts fleet-wide, unseen until a release
- **Severity:** 🟡 Sharp edge
- **Filed by:** mgf-fed supervisor (cockpit) on 2026-07-03
- **What hurts:** The canonical CI lint step runs `ruff check src tests` (the linter) but **never** `ruff format --check`. So formatting can drift into committed files with CI staying green. The only gate that catches it is the *local* release gate (`mgf-fed release-local`, whose `_drift` runs `ruff format --check`) — so drift surfaces only at release time, per-repo, on whatever ruff version the maintainer happens to have.
- **Why it matters:** On the mgf-fed 0.11.1 release, 3 test files carried accumulated ruff-format drift that CI had passed for weeks; the release **blocked**, needing a separate unblocker PR (mgf-fed #69). This is fleet-wide — every repo's format is ungated in CI — and it couples releasability to the maintainer's local ruff (pin is a wide `>=0.4,<0.16`).
- **Evidence:** `templates/woodpecker/base.yml:39` (`- ruff check src tests`; no `ruff format --check` in `base.yml` or any `frag-*.yml`). The gate that *does* catch it: mgf-fed `_drift` ("ruff format-stability pin"). Repro: mgf-fed PR #69 `chore(format): ruff-format 3 drifted test files` — `tests/test_closed_box.py`, `tests/fleet/refresh_lock.py`, `tests/fleet/test_fleet_coinstall.py` all drifted while CI was green.
- **Suggested shape:** Add `- ruff format --check src tests` to the lint step in `base.yml` (right after `ruff check`). Roll out as a cohort (like AP-16) — a fleet re-scaffold will surface existing format drift, each repo format-cleans or is already clean. Consider pinning ruff to an **exact** fleet version (not a range) so the format baseline is deterministic across machines + CI.
- **Status:** OPEN

### PAPER-116 — Canonical gitleaks template has no `.claude/` allowlist; repos with agent config carry a justified carve-out that reads as scaffolding drift
- **Severity:** 🟢 Nice-to-have
- **Filed by:** mgf-fed supervisor (cockpit) on 2026-07-03
- **What hurts:** `templates/gitleaks.toml` has no allowlist entry for `.claude/`. Repos that keep Claude Code agent config (`.claude/` — permission allowlists / settings embedding credential-**shaped** command patterns with placeholder tokens, e.g. `Authorization: token __TRACKED_VAR__`) must add a `'''\.claude/'''` allowlist path or gitleaks' curl-auth-header rule re-flags the placeholders. mgf-common **and** mgf-standard both carry this justified entry — and `mgf-fed drift-check` reports it as `[exact] .gitleaks.toml differs from template/gitleaks.toml`.
- **Why it matters:** Two of the fleet's most central repos show as "drift" for a correct, security-neutral carve-out — noise that trains maintainers to ignore drift-check, with no clean resolution (removing the entry re-introduces false-positive leak flags; the file can't match the template without losing the protection).
- **Evidence:** `mgf-common/.gitleaks.toml` + `mgf-standard/.gitleaks.toml` each add `'''\.claude/'''` under `[allowlist].paths` (absent from `templates/gitleaks.toml`, ~line 45). `mgf-fed drift-check` → `✗ mgf-common (2)` / `✗ mgf-standard (1)`, item `[exact] .gitleaks.toml differs from template/gitleaks.toml`.
- **Suggested shape:** Add `'''\.claude/'''` (with the placeholder-token rationale comment) to the canonical `templates/gitleaks.toml` allowlist — `.claude/` is now a fleet-standard directory, so every repo should allowlist it; roll out via the SH-05 gitleaks scaffold path. (Companion filed against mgf-fed: make drift-check's `.gitleaks.toml` `[exact]` rule tolerant of *additive* per-repo allowlist entries.)
- **Status:** OPEN
