# FEEDBACK action plan — v1.0 readout

**Generated:** 2026-05-19. **Last updated:** 2026-05-20.
**Status:** steps 1–6 of the §10 sequence COMPLETE; step 7 (Q-2 / Q-4 /
Q-11) is the only remaining pre-cut work. See §10 for the live checklist.
**Source:** [`FEEDBACK.md`](../../FEEDBACK.md) (64 entries).
**Scope:** mgf-common only. Sibling-scoped papers (PAPER-36/37 → mgf-http,
PAPER-39…42 → mgf-apiprobe) are MOVED redirects and live in those siblings.
**Reader:** maintainer (Bassam) — read alongside
[`v1_0_handoff.md`](v1_0_handoff.md) §3.

---

## §0 TL;DR

64 entries total. **45 SHIPPED**, **6 MOVED**, **1 DEFERRED**, **1 ACCEPTED-staged
(ASPIRE-06, locks at v1.0)**, **11 OPEN** (after de-duplication, see §6).

Of the 11 OPEN entries, this plan flags (post-review-pass 2026-05-19):

- **1 soft v1.0 gate** (additive kwarg; cheap either side of v1.0 but
  cleaner at v1.0): PAPER-45 (`JsonFormatter(field_map=)`).
- **2 entries that default to REJECT-WITH-RATIONALE** after code-level
  verification: PAPER-46 (proposes adding `context` to `AppError` —
  contradicts EH-08's typed-attribute pattern; field doesn't exist today),
  PAPER-49 (assumes pydantic is isolated to `BaseAppSettings` — actually
  load-bearing across 7 modules / 4 subsystems).
- **1 affirmation**: PAPER-10 stays DEFERRED at v1.0 — none of its
  three reopen criteria have fired.
- **6 ship-now-cheap doc fixes** (PAPER-31, 32, 33, 34, 35, 48): pure
  prose, no API risk, high consumer value.
- **3 tooling fixes** worth catching before v1.0 because the v1.0 onboarding
  funnel runs through them: PAPER-54 (catch-up DOC-07 path), PAPER-57
  (federation-sync message), PAPER-52 (catch-up onboarding pin window).
- **7 v1.x items** safe to defer: PAPER-55, 47, 56, 58, 50, 51, 53.
- **1 post-v1.0 aspirational**: ASPIRE-05 (cross-language standards split).
- **1 governance issue (RESOLVED 2026-05-19)**: PAPER-45..49 had been used twice
  (5 ID collisions); vm-vmanager-app's later-filed batch renumbered to PAPER-54..58
  in-place. See §6.

Recommended sequence: §6 renumber ✓ → §3 (doc bundle) → §4 (catch-up bundle) →
§2 (close out Q-12 / Q-13 / Q-14 — most likely outcome: REJECT / REJECT /
SHIP-A) → §5 (Q-2/Q-4/Q-11 from `v1_0_handoff.md`) → v1.0 tag.

---

## §1 Method

For each OPEN entry, the plan extracts:

- **Severity** (from the entry's own frontmatter — not re-graded here).
- **Surface class**: API / tooling / docs / process.
- **Submitter** — to honour the consumer-perspective signal that the
  feedback process exists for. Independent consumers (non-reference) carry
  more weight per `FEEDBACK.md §1.5`.
- **v1.0 implication**: does deferring this past v1.0 cost a breaking
  change later, or is it additive forever?
- **Recommendation**: ship-now, ship-in-v1.0, defer-to-v1.x, or post-v1.0.

The severity scores in `FEEDBACK.md` already reflect the consumer's view.
This plan does not re-score; it only adds the v1.0-timing lens that the
consumer can't see from a single-sibling vantage point.

---

## §2 v1.0 API gates (decide before tag)

These three entries touch the public surface in ways that are cheap to
adjust pre-v1.0 and expensive post-v1.0. Each deserves a yes/no decision
in the same wave as Q-2/Q-4/Q-11.

### §2.1 PAPER-46 — `AppError.context` immutability  🟢 REJECT-WITH-RATIONALE (default)

- **Submitter:** inthewords (independent web consumer, 2026-05-18).
- **Code-level verification (2026-05-19 review pass):**
  `src/mgf/common/exceptions/_base.py` defines AppError as
  `class AppError(Exception): pass` — **no `context` field exists**.
  The class docstring (lines 30–46) mandates EH-08's typed-attribute
  pattern: subclasses set machine-readable attributes (`self.argv`,
  `self.returncode`, etc.) in `__init__` BEFORE `super().__init__(message)`.
  PAPER-46 isn't fixing a foot-gun on existing API — it's a proposal
  to **add** a generic `context` field that departs from EH-08.
- **Stability-audit context:** `mgf.common.exceptions` ships v1.0 with
  35 stable names + 1 promotion (`add_help_note`). AppError is one of
  the 35 already-stable. Adding `context` to a stable surface is
  scope creep, not foot-gun mitigation.
- **inthewords' `.context.update` sites** were caught by their OWN
  lint (`TestNoRawDjangoExceptionsInViews`). That's the correct
  outcome: EH-08 specifies the pattern, project-level lints enforce
  it. The mgf-common library's job here is to make EH-08 more
  discoverable, not to add a competing API.
- **Recommendation:** **REJECT** PAPER-46 with the rationale above.
  Close-loop notification to inthewords: thank them for the signal;
  point at EH-08's typed-attribute pattern; offer to add a recipe
  showing how to subclass AppError with typed diagnostic fields
  (e.g., `ViewError(action, user_id)` rather than
  `AppError(message, context={'action': ..., 'user_id': ...})`).
- **Re-open trigger:** if a second consumer files the same friction
  AND can't express their case via EH-08 typed-attribute subclasses,
  reopen and revisit. Single-consumer signal + EH-08 conflict =
  REJECT for now.

### §2.2 PAPER-49 — Pydantic split into `mgf-common[settings]` extra  🟢 REJECT-WITH-RATIONALE (default)

- **Submitter:** inthewords.
- **Concern (as filed):** `pip install mgf-common` pulls pydantic +
  pydantic-core (~3 MB binary) for the exception/log/CLI consumers
  who never touch `BaseAppSettings`.
- **Code-level verification (2026-05-19 review pass):** the submitter
  assumed pydantic is isolated to `BaseAppSettings`. Reality —
  pydantic is imported in **7 modules across 4 subsystems**:

  ```
  src/mgf/common/vault/_configs.py          # vault backends
  src/mgf/common/versioned/_store.py        # versioned file store schema
  src/mgf/common/cli/_validate.py           # mgf-common validate
  src/mgf/common/cli/_provenance.py         # mgf-common provenance
  src/mgf/common/config/_settings.py        # BaseAppSettings (expected)
  src/mgf/common/config/_loader.py          # layered loader
  src/mgf/common/config/_pyproject_source.py
  src/mgf/common/_env_source.py
  src/mgf/common/settings.py                # re-export
  ```

  Splitting cleanly would require multiple extras
  (`[settings]`, `[vault]`, `[versioned]`, `[cli-validate]`) — a
  multi-axis install matrix that's far harder to communicate than
  the current single-install shape. The "~50 LOC + lazy imports"
  estimate in the action-plan v1 was wrong; this is a sub-system
  refactor.
- **Trade-off:** ~3 MB disk + ~100ms import savings vs. a 4-extra
  install matrix that every existing consumer (vm-vmanager-app,
  PlasmaMapper, inthewords, iosRecovery) has to learn and pin.
  pydantic is the de-facto schema library across mgf-common; the
  cost / benefit doesn't pencil out.
- **Recommendation:** **REJECT** PAPER-49 with the rationale above.
  Close-loop notification to inthewords: keep maximal install at
  v1.0; document the pydantic-coupling explicitly in
  `COMMON_COMPONENTS.md` so future thin-consumer evaluators can
  size the cost before adoption.
- **Re-open trigger:** PEP 711 (pylock.toml) or a pydantic 3.x
  release that materially changes the disk-footprint math, OR a
  second consumer (not inthewords) files concrete deploy pain
  (cold-start latency at scale, not theoretical) from pydantic in
  the install. Single-consumer disk-size signal = REJECT for now.

### §2.3 PAPER-45 — `JsonFormatter(field_map=)`  🟡 SOFT GATE

- **Submitter:** inthewords.
- **Concern:** Monolithic wire shape `{ts, level, logger, msg, ...}`
  with no rename map. Consumers with deployed Grafana/ELK/Datadog
  parsers either re-deploy parsers same-hour as the code change
  (operationally expensive) or shim a ~250-line in-house formatter
  forever. inthewords took the shim path; their non-adoption is
  documented but the maintenance cost is real.
- **v1.0 implication:** Approach A (`field_map=` kwarg) is purely
  additive — safe in v1.x. Approach C (split into
  `Redactor` + `OTelEnricher` + `JsonEncoder`) is a **constructor
  shape change** and is much harder post-1.0.
- **Code-level verification (2026-05-19 review pass):**
  `src/mgf/common/observability/json_formatter.py:78` docstring
  itself says "Output schema (top-level keys; **never rename without
  a MAJOR bump**)" — the submitter correctly identified a closed
  surface. Adding `field_map={}` is purely additive; the default
  (no rename) preserves current behaviour.
- **Recommendation:** **Ship Approach A in v1.0** (one kwarg, ~20 LOC
  additive, zero migration risk). **Schedule Approach C as the
  v1.x→v2.0 candidate** if a second consumer hits the same shape
  mismatch. Reject Approach B unless a second consumer asks.

---

### §2.4 PAPER-10 — Entry-point plug-in auto-discovery  🟢 AFFIRM DEFERRED

- **Status today:** DEFERRED since 2026-05-02; reopen-gated.
- **Reopen criteria (per `FEEDBACK.md` §PAPER-10):**
  1. A plugin-framework consumer files concrete pain from the
     explicit-call shape.
  2. A second independent consumer files extension-wiring pain (or
     non-pain — both count).
  3. An in-tree consumer ships a pip-installable extension that
     would benefit from auto-discovery.
- **2026-05-19 status:** none of the three triggers have fired. The
  four current consumers (vm-vmanager-app, PlasmaMapper, inthewords,
  iosRecovery) all use the explicit-call API without complaint.
  Strategic-review P1 ("second, independent consumer") was nominally
  satisfied by inthewords, but inthewords hasn't filed
  extension-wiring evidence either way.
- **v1.0 action:** **affirm DEFERRED in the v1.0 cutover note.**
  Restate the reopen criteria so post-v1.0 consumers know the
  signal we're watching for. The explicit-call API is already
  additive-compatible with any future entry-point walk, so
  reopening later costs no migration on existing consumers.
- **No action item beyond the affirmation paragraph in `CHANGELOG.md`
  v1.0 section.**

---

## §3 Doc bundle — ship now, before v1.0

Six pure-prose fixes. All cheap. All cited by the conformance-audit
exercise that's increasingly the norm across consumers. Bundle them
into one PR titled `docs: PAPER-31/32/33/34/35/48 — audit-pattern
discoverability bundle`.

| ID | Title | Submitter | Cost |
|---|---|---|---|
| **PAPER-31** | Audit-outcome taxonomy: separate N/A from declined-with-rationale from no-surface-yet | PlasmaMapper | one new principles file or addendum |
| **PAPER-32** | Web-shape carve-outs need inline cross-refs from each affected rule (CF-09, LG-06, LG-10) | PlasmaMapper | one paragraph appended per ~4 rules |
| **PAPER-33** | `BaseAppSettings.version: int = 1` auto-provision invisible to CF-03 audits | PlasmaMapper | one paragraph on CF-03 (+ CF-02, CF-06, CF-08) |
| **PAPER-34** | `scan-deps` recipe candidate for `COMMON_COMPONENTS.md` | PlasmaMapper | one new `docs/recipes/scan-deps.md` from PlasmaMapper's SH-25 |
| **PAPER-35** | Federation-aligned releases need an aggregated consumer cutover guide | PlasmaMapper | one-time template + retroactive v0.34 fill |
| **PAPER-48** | Standards corpus has no browsable URL surface; rule IDs not citeable | inthewords | mkdocs or sphinx render of `docs/standards/`; one-time infra |

PAPER-48 is the heaviest — it's docs *infrastructure*, not docs *prose*.
It can ship as a v1.0 deliverable OR slip to v1.1 without harm, as long
as the rule IDs themselves stay anchor-stable inside the existing
markdown. **Recommendation:** ship anchor-stable markdown in v1.0; ship
mkdocs render in v1.1.

PAPER-32 and PAPER-31 also unblock PAPER-47 (the `--kind=` CLI
filter) — see §4.

---

## §4 Catch-up / tooling bundle — onboarding funnel cleanup

Every new consumer in v1.0+ hits `mgf-fed catch-up` and the
federation-sync message. Three OPEN items pollute that funnel today.

### §4.1 SHIP-IN-v1.0 (blocking v1.0 quality)

| ID | Title | Submitter | Why v1.0 |
|---|---|---|---|
| **PAPER-54** | catch-up hardcodes `docs/CLAUDE.md`, but DOC-07 says `docs/claude/CLAUDE.md` | vm-vmanager-app | spec-conformant consumers cannot reach catch-up exit-0. Federation-sync message recommends catch-up as L2/L3 gate. Blocking. |
| **PAPER-57** | Federation-sync message recommends `>=0.37,<0.38` while 0.38 is the federation-contract release | vm-vmanager-app | every consumer hits this once. Template fix. Trivial. |
| **PAPER-52** | catch-up onboarding text hard-codes `>=0.37,<0.38`; should template from `__version__` | iosRecovery | same class as PAPER-57. Trivial. Same PR makes sense. |

Approach: bundle into one PR. PAPER-54 Approach A (accept both paths,
audit canonical, info-row the legacy). PAPER-57 + PAPER-52 share a
`__version__`-templating utility.

### §4.2 SHIP-IN-v1.x (post-tag, not v1.0-blocking)

| ID | Title | Submitter | Reason |
|---|---|---|---|
| **PAPER-55** | catch-up `--apply` silently creates duplicate `docs/CLAUDE.md` | vm-vmanager-app | UX papercut. Fix follows naturally after PAPER-54. |
| **PAPER-47** | `standards --kind=django` filter for `applies_to:` annotations | inthewords | additive flag; depends on PAPER-32 audit-side framing. v1.1 candidate. |
| **PAPER-56** | `USERS.md` surface attributions drift; auto-verify from consumer source | vm-vmanager-app | federation hygiene; pure tooling. v1.1. |
| **PAPER-58** | `doctor` flags `[vault]` extra missing for BYO-vault consumers | vm-vmanager-app | downgrade `⚠ → ℹ` for `vault` only — one-line change, but no API surface, v1.1 safe. |
| **PAPER-50** | LG-12 perf-test budget (100 µs/record) brittle across hardware | vm-vmanager-app | test ergonomics; consumers can pin their own threshold today. v1.1 or later. |
| **PAPER-51** | Deprecation-cascade detection: catch-up should audit source for deprecated names BEFORE the pin bump | iosRecovery | additive audit row. **Data API is already 90% done** — `_N818_DEPRECATED_ALIASES` exists at `src/mgf/common/exceptions/__init__.py:100`; PAPER-51 Approach A only needs to expose it as `mgf.common.deprecations.entries() -> dict[str, str]` (~5 LOC). v1.1 high-value, especially because Q-2 (N818 renames) GROWS this registry by 12 entries — the audit row becomes proportionally more valuable as a result. **Also fix the "since v0.39.0" wording at `exceptions/__init__.py:121` — that's a stale-string today.** |
| **PAPER-53** | `mgf-common init-consumer <name> <kind>` for one-command sibling skeleton | iosRecovery | net-new subcommand; ASPIRE-06 surface stabilises in v1.0 so templates can land cleanly in v1.1. |

PAPER-51's "since v0.39.0" wording bug is a 2-line fix that should
ship in v1.0 even if the full audit-row work waits. Track it as a
v1.0-quality micro-item.

---

## §5 Aspirationals — confirm trajectory

| ID | Title | Status | v1.0 action |
|---|---|---|---|
| **ASPIRE-05** | mgf-common as cross-language engineering-process source-of-truth | OPEN — *explicitly post-v1.0* per entry | none. Entry itself argues for deferral until 2+ non-Python siblings exist. v1.0 doesn't foreclose any option. |
| **ASPIRE-06** | Federated package architecture (mgf-fastapi/http/sqlalchemy/alembic/django) | ACCEPTED — all 4 extractions shipped (v0.28–v0.31); locks at v1.0 | confirm sibling-pin discipline + cross-check that the experimental→stable promotion list in `v1_0_stability_audit.md §3` is consistent with the locked surface. |

ASPIRE-06's retrospective is the natural place to record "v1.0 ships
with federated surface as designed; no regressions found in v0.32–v0.38
stabilisation window."

---

## §6 Governance: duplicate IDs — RESOLVED 2026-05-19

`FEEDBACK.md` had **5 ID collisions** — both batches filed
on 2026-05-18 by independent consumers within the same submission window.
Two consumers branched from the same last-ID state, producing two parallel
PAPER-45..49 sequences. The earlier-in-file batch (inthewords) was kept;
the later batch (vm-vmanager-app) was renumbered to PAPER-54..58 in-place:

| Old ID | Submitter / topic | New ID |
|---|---|---|
| `PAPER-45` (line 4865) | vm-vmanager-app — catch-up DOC-07 path | **`PAPER-54`** |
| `PAPER-46` (line 4895) | vm-vmanager-app — `--apply` duplicate file | **`PAPER-55`** |
| `PAPER-47` (line 4926) | vm-vmanager-app — USERS.md drift | **`PAPER-56`** |
| `PAPER-48` (line 4974) | vm-vmanager-app — federation-sync pin window | **`PAPER-57`** |
| `PAPER-49` (line 5006) | vm-vmanager-app — `doctor` vault warning | **`PAPER-58`** |

Two in-entry cross-references inside the renumbered batch were also
updated (the "Sister-paper to PAPER-45" and "if PAPER-45 ships"
references inside the entry that was PAPER-46-vm-vmanager-app / now PAPER-55
both point at PAPER-54). The inthewords set kept its IDs and no
cross-refs into it needed touching. Git history is the audit trail.

**Process fix to prevent recurrence (FOLLOW-UP, not yet shipped):** add
a check to the `mgf-fed feedback-howto` CLI that scans `FEEDBACK.md` for
duplicate `^#### ` IDs and fails with a one-line suggestion. Small bug,
large value — protects the federation-wide audit trail. Track as a
v1.x candidate (it depends on the same registry that PAPER-51 needs).

---

## §7 Decisions you (the maintainer) need

Three policy calls already live in `v1_0_handoff.md §3` (Q-2 / Q-4 /
Q-11). This action plan adds **three more** scoped to OPEN feedback.
Default leans below are post-review-pass (2026-05-19) after
code-level verification.

### Q-12: PAPER-46 — `AppError.context` — REJECT (default lean)

- **REJECT (recommended).** Verification shows AppError has no
  `context` field today; this is a feature proposal, not a foot-gun
  fix. The proposal contradicts EH-08's typed-attribute pattern.
  Close PAPER-46 with rationale; offer inthewords a recipe for
  typed-attribute AppError subclasses.
- Accept (against recommendation): only if maintainer wants to
  formally deviate from EH-08. Requires a parallel EH-08 amendment
  PR — the standards corpus can't simultaneously mandate typed
  attributes AND ship a `context` bag on the base class.

### Q-13: PAPER-49 — pydantic extras split — REJECT (default lean)

- **REJECT (recommended).** Verification shows pydantic is
  load-bearing across 7 modules / 4 subsystems (vault, versioned,
  config, cli). The submitter's "isolated to BaseAppSettings"
  premise is wrong; splitting requires a 4-extra install matrix.
  Disk + import savings (~3 MB, ~100 ms) don't justify the
  ergonomics tax. Close PAPER-49 with rationale; document the
  pydantic-coupling in `COMMON_COMPONENTS.md`.
- Accept (against recommendation): only if a second consumer files
  concrete deploy-time pain. Then re-scope to multi-extra and budget
  for a sub-system refactor.

### Q-14: PAPER-45 — `JsonFormatter(field_map=)` — SHIP-A (default lean)

- **SHIP Approach A in v1.0 (recommended).** Additive kwarg
  (~20 LOC); default `{}` preserves current behaviour. Verification
  confirms the docstring itself says "never rename without a MAJOR
  bump" — closed surface, real gap. Approach C (split into
  Redactor + OTelEnricher + JsonEncoder) is a v2.0 candidate.
- Defer A to v1.1: low-cost option; the kwarg is additive in either
  window. Pick this if the v1.0 wave is tight.
- Reject: only if maintainer treats the closed-shape promise as
  load-bearing for log-aggregator stability.

If the default leans hold (REJECT / REJECT / SHIP-A), **v1.0's API
delta from this plan is one additive kwarg on JsonFormatter.** That's
a one-MR change that can land alongside the Q-2 N818 wave without
extending the v1.0 cut.

---

## §8 Cross-references

- [`v1_0_handoff.md`](v1_0_handoff.md) — Q-2 / Q-4 / Q-11 maintainer
  decisions. This action plan adds Q-12 / Q-13 / Q-14.
- [`v1_0_stability_audit.md`](v1_0_stability_audit.md) §3 — promotion
  worksheet. PAPER-46 + PAPER-49 + PAPER-45 affect 2 rows of that
  table (AppError, BaseAppSettings, JsonFormatter — see audit doc for
  current promotion proposal).
- [`v1_0_quality_gates.md`](v1_0_quality_gates.md) — Q-2 (N818) and Q-4
  (PyPI). The PAPER-51 "since v0.39.0" wording bug should add itself
  as a Q-2 sibling micro-gate.
- [`pre_release_additions.md`](pre_release_additions.md) §10 — parked
  candidates. Cross-check that nothing here duplicates that list.
- [`../../FEEDBACK.md`](../../FEEDBACK.md) — source.

---

## §9 What this plan does NOT do

- It does NOT change `FEEDBACK.md`. Status flips, decisions, and
  retrospectives belong in `FEEDBACK.md` itself per the lifecycle in
  §1.3 of that doc.
- It does NOT bump any version. v1.0 is the maintainer's call per
  `v1_0_handoff.md §7`.
- It does NOT make the Q-12 / Q-13 / Q-14 decisions. Those are policy
  calls; this plan recommends but does not commit.
- It does NOT touch sibling FEEDBACK.md files. mgf-http, mgf-apiprobe,
  mgf-fastapi, mgf-sqlalchemy, mgf-alembic, mgf-django each carry
  their own. PAPER-36/37/39/40/41/42 are MOVED redirects only.

---

## §10 Suggested sequence to v1.0

**Execution status (updated 2026-05-20): steps 1–6 are COMPLETE.
Step 7 (Q-2 / Q-4 / Q-11) is the only remaining pre-cut work and
is the maintainer's call. Commits `6952544` → `00ab30b` on `main`.**

1. ✅ **Renumber duplicate IDs** (§6) — done 2026-05-19, commit `6952544`.
2. ✅ **Doc bundle** (§3): PAPER-31 / 32 / 33 / 34 / 35 +
   anchor-stable markdown for PAPER-48 — commit `6952544`.
3. ✅ **Catch-up bundle** (§4.1): PAPER-54 / 57 / 52 + the
   "since v0.39.0" wording fix from PAPER-51 — commit `555be0f`.
   (Plus `c300bc0` — CHANGELOG[0.34.0] corrigendum.)
4. ✅ **Q-12 / Q-13 / Q-14 decided** (§7) — maintainer confirmed
   the default leans: Q-12 REJECT, Q-13 REJECT, Q-14 SHIP-A.
   `JsonFormatter(field_map=)` shipped; PAPER-46 + PAPER-49 flipped
   to REJECTED with rationale — commit `b7547c3`.
5. ✅ **PAPER-46 + PAPER-49 close-loop notifications** — drafted
   inline in each entry's retrospective per `RELEASING.md` REL-09,
   commit `b7547c3`.
6. ✅ **PAPER-10 affirmation** — in `CHANGELOG.md [Unreleased]`,
   reopen criteria restated, commit `b7547c3`.
7. ⏳ **Maintainer decision** on Q-2 / Q-4 / Q-11 (`v1_0_handoff.md`).
   Q-4 (PyPI) resolved: private PyPI server for the foundation's
   private circle; public PyPI deferred. Q-2 (N818 rename) +
   Q-11 (stability promotion sign-off) still pending.
8. ☐ **v1.0 cut** per `v1_0_handoff.md §4`.
9. ☐ **Post-v1.0 backlog** (§4.2): PAPER-55 / 47 / 56 / 58 / 50 /
   51 (full audit-row) / 53 + the §6 follow-up duplicate-ID CLI check.
   Each is a normal v1.x minor feature.
10. ☐ **Post-v1.0 aspirational** (§5): ASPIRE-05 stays watching for
    the second non-Python sibling.

**Pre-cut housekeeping (not v1.0-blocking, done 2026-05-20):** ruff
backlog swept clean (Q-3, commit `f28271e`); mypy --strict +
federation test sweep green (1646 / 0); PUBLIC_API.md / .json sync
verified; cross-link + FEEDBACK.md consistency passes done; the
library-first strategy stated in `FEDERATION.md §1.1` (commit
`00ab30b`).

The net API-surface delta from this plan was exactly one additive
kwarg (`JsonFormatter(field_map={})`) — Q-12 + Q-13 closed as
REJECT, so no other surface moved. v1.0 is reachable once the
maintainer signs off Q-2 + Q-11 and runs the cut.
