# PROPOSAL — Federation test-supervisor adoption + extraction path

**Status:** DRAFT — pending maintainer ack
**Author:** drafted 2026-05-26 from research across `mgf-hrb` (the originating project) + `mgf-common` (the cornerstone where the spec would land)
**Triggers:** mgf-hrb shipped a production-grade test supervisor (`scripts/_mtest/` — 2,210 LOC stdlib-only) and proposed TS-22..27 federation-wide adoption via `MGF_COMMON_ADOPTION_MESSAGE.md`. The maintainer asked: *"is this something can be implemented in this library, or better in a sibling library?"*

This document answers that question, designs the phased adoption, drafts the standards changes, and codifies the extraction-to-sibling trigger.

---

## §0 — TL;DR (one screen)

**Recommendation:** **Adopt the spec immediately in mgf-common (sibling #1) via copy-paste, ship a thin `mgf.common.testing` importable module for the portable types, and codify a clear extraction trigger for the future `mgf-test-supervisor` sibling.** Do NOT publish a new sibling library today.

**Three deliverables, all in mgf-common:**

1. **Standards** — add **TS-22..TS-27** (six new L1 rules) to `docs/standards/TESTING.md`. The supervisor's behaviour becomes federation-binding regardless of implementation.

2. **Implementation** — copy-paste `scripts/_mtest/` from mgf-hrb into mgf-common's `scripts/_mtest/`. mgf-common is now sibling #1 adopter. Wire it via a new `mgf-common test` CLI subcommand. The wheel is unchanged (the supervisor lives in `scripts/`, not `src/`).

3. **Importable surface** — promote the genuinely-reusable types (JSONL schema, `default_sock_path` parallel-safe helpers, tier/budget primitives) to `src/mgf/common/testing/` so consumers can read run.jsonl + share marker→tier mappings without copy-pasting. Five new public names added to `mgf.common.__all__`.

**Extraction trigger codified:** when a **second** federation sibling adopts the supervisor via copy-paste (i.e., `scripts/_mtest/` lands in their repo), that is the signal to extract `mgf-test-supervisor` as its own sibling library. The maintainer's "≥2 adoptions before extract" discipline is preserved + made auditable.

**Why this answer:**
- The maintainer explicitly chose "wait for ≥2 siblings before extracting" — going straight to extraction overrides their stated discipline without new evidence.
- Copy-paste in mgf-common validates the abstraction across one independent context before the cost of a new published package + release cadence + dep matrix lands.
- The spec (TS-22..27) propagates immediately even though the implementation doesn't — every consumer learns the bar even if they're not running the supervisor yet.
- The importable `mgf.common.testing` surface gives consumers the SHAPED types (read run.jsonl, share marker→tier) without forcing them onto the supervisor itself.

---

## §1 — Background: what mgf-hrb built

A 2,210-line **stdlib-only** test supervisor that runs pytest as a subprocess. Lives at `mgf-hrb/scripts/_mtest/` (10 Python files) plus a `scripts/mtest` venv-aware bootstrap wrapper. Core innovation: **pytest runs in a child process** so a pytest crash, hang, or segfault is recorded as a structured outcome rather than killing the test driver itself.

### §1.1 The CLI surface (`mtest`)

| Flag / subcommand | Purpose |
|---|---|
| `mtest [--tier 0\|0-3]` (default: all tiers) | Run selected tier(s) |
| `mtest --budget 30s\|5m\|3600` | Greedy knapsack: fit selected tests inside time budget; tier-0 always included |
| `mtest --skip-quarantined` (default) | Skip flaky tests |
| `mtest --quarantine-attempt` | Include quarantined tests + track passes toward eviction |
| `mtest --resume` | Continue from last interrupted run via `.gates/state.json` |
| `mtest --fail-fast` | Stop after first failed chunk |
| `mtest --self-check` | Verify supervisor itself (canned pass/fail/hang/crash tests) |
| `mtest --report` | Show last run's `.gates/last-run/summary.md` |
| `mtest --quarantine` | Show current `.gates/quarantine.json` |

Exit codes: `0` clean / `1` test(s) failed / `2` supervisor itself failed / `3` usage error / `4` self-check failed.

### §1.2 The `.gates/` directory shape

```
.gates/
├── last-run/
│   ├── run.jsonl             ← one JSON record per test outcome (append-only)
│   ├── summary.md            ← human-readable end-of-run summary (overwritten)
│   ├── failures/<id>.txt     ← per-failure bundle (see §1.3)
│   ├── junit-NNNN.xml        ← raw pytest XML chunks (debug aid)
├── state.json                ← resume checkpoint
├── durations.json            ← per-test rolling 5-run duration history
└── quarantine.json           ← flake state
```

**JSONL record (schema_version "1.0.0"):**
```json
{
  "test_id": "tests/foo.py::test_bar",
  "outcome": "passed|failed|error|skipped|hung|crashed|quarantined",
  "severity": "OK|WARN|FAIL|SKIP",
  "duration_ms": 123,
  "tier": 0,
  "markers": ["smoke"],
  "code": "AssertionError|WALL_CLOCK_HUNG|PROCESS_CRASHED|...",
  "summary": "...", "detail": "...", "why": "...", "remediation": "...",
  "captured_stdout": "last 4 KB",
  "captured_stderr": "last 4 KB",
  "run_id": "run-20260526T...", "ts_iso": "2026-05-26T...",
  "schema_version": "1.0.0"
}
```

Schema deliberately mirrors `mgf.common.observability.DoctorFinding` so downstream tooling (a future "Magogi dashboard") can use one parser.

### §1.3 The per-failure detail bundle

`.gates/last-run/failures/<test-id-safe>.txt` carries — **in one file** — every diagnostic an operator could possibly want:

- Traceback (deduplicated to the failing test)
- Captured stdout + stderr (last 4 KB each)
- Live process state: PID, wchan, thread count from `/proc/<pid>/`
- `dmesg` tail (last 30 lines, best-effort)
- Environment fingerprint: Python version, install path, git HEAD + branch, toolchain versions
- Last 5 outcomes of this test (history from `durations.json`)
- Chunk wall-time + exit code + signal name
- Pointer to `DIAGNOSING_HANGS.md` for recipes

No more scrolling through pytest output to assemble the picture.

### §1.4 The 4-tier budget model

```python
# tiers.py
TIERS = (
    TierBudget("tier-0", 0.10, 1.0,    "Build-broken signal"),
    TierBudget("tier-1", 0.30, 30.0,   "Daily contract"),
    TierBudget("tier-2", 2.00, 120.0,  "Promise pins"),
    TierBudget("tier-3", 10.0, 1800.0, "Long signal (stress / perf / e2e)"),
)

MARKER_TIER = {
    "smoke": 0,
    "contract": 1, "property": 1, "regression": 1, "fuzz": 1,
    "conformance": 2, "use_case": 2,
    "perf": 3, "stress": 3, "e2e": 3,
}
```

A test inherits its tier from its strongest marker; explicit `@pytest.mark.tier(N)` overrides. The auto-tier heuristic bumps a test up one tier if its last-5 median is ≥5× the tier's typical budget.

### §1.5 Auto-quarantine

Sliding window of last 5 outcomes per test. **3+ failures within the window → auto-quarantine.** Quarantined tests are skipped by default. `mtest --quarantine-attempt` includes them and tracks consecutive passes — **3 consecutive passes → evict from quarantine** + returns the test to the normal pool. Atomic JSON state in `.gates/quarantine.json`.

### §1.6 Timeout / faulthandler harness (in `pyproject.toml`)

```toml
timeout = 30                          # per-test 30s deadline
timeout_method = "thread"             # dumps every thread's stack on timeout
faulthandler_timeout = 60             # 2× timeout; SIGABRT safety net if pytest-timeout itself hangs
```

Defense in depth: pytest-timeout (thread-based, gives a Python-side dump) + faulthandler (signal-based, kernel-level dump if even the timeout thread wedges).

---

## §2 — Where does this belong? Three options analysed

| Option | Description | Verdict |
|---|---|---|
| **A. In mgf-common only** | Copy-paste into `mgf-common/scripts/_mtest/`; spec at TS-22..27; never extract. | ❌ — discards the maintainer's stated "extract when ≥2 siblings adopt" discipline; mgf-common is a runtime cornerstone, not a dev-tool hub. |
| **B. Extract immediately as `mgf-test-supervisor`** | New sibling library, PyPI-published, every consumer pip-installs. | ❌ — overrides the maintainer's stated discipline without evidence; single-adopter abstractions calcify wrong shapes. |
| **C. Phased: copy-paste in mgf-common now → extract when sibling #2 adopts** | mgf-common is sibling #1 today; spec is binding; extraction trigger codified. | ✅ — **recommended**. Honors discipline, ships the spec immediately, gives the abstraction one independent test before publishing. |

### §2.1 Why not just keep it in mgf-hrb

The supervisor is generic — nothing in `_mtest/` is hrb-specific except three knobs (broker process name in `diagnostics.py`, the build command in `tests/_broker.py`, and a Zig-toolchain check). The 99% portable part being trapped in one project's `scripts/` directory is the textbook reason the federation extracts cornerstone infrastructure.

### §2.2 Why not jump straight to `mgf-test-supervisor` extraction

Two reasons:

1. **The maintainer's discipline is correct.** Per their `MGF_COMMON_ADOPTION_MESSAGE.md`: *"Each project owning its own copy means a supervisor change in one project doesn't auto-break others; the spec (TS-22..TS-27) holds the line."* Extracting before the abstraction has been validated by ≥2 independent adoptions risks shipping a frozen design that the second consumer would have wanted differently.
2. **mgf-common adoption is the first real test of the abstraction.** mgf-common has no broker, has different build needs, different marker taxonomy emphasis (more property/contract, less stress/e2e), and is itself a library — not an application. If `_mtest/` adapts cleanly with only the documented knobs flipped, the abstraction is proven for sibling #2.

### §2.3 Why ALSO ship `mgf.common.testing` (importable) at the same time

Some pieces of the supervisor are **library-shaped** even before the whole thing is extracted:

- The JSONL schema (`TestRecord` dataclass, severity / outcome enums) — consumers will want to read `run.jsonl` from anywhere; freezing the schema as a public type prevents downstream divergence.
- `default_sock_path()` / parallel-worker discrimination — needed by every consumer that runs pytest-xdist; trivial to share.
- Marker → tier mapping — needs to be inspectable + extensible per consumer.
- The failure-bundle schema constants — so downstream dashboards know the shape.

These DON'T need to be copy-pasted. They graduate to `mgf.common.testing` now as `stable (v0.40.0)`. The supervisor (which calls them) continues to live in `scripts/_mtest/` and copy-paste.

---

## §3 — The plan (3 phases, ~1 day of work each)

### Phase 1 — Standards (TS-22..TS-27) — ships first, no code needed

**File:** `docs/standards/TESTING.md` (extends the existing TS-01..TS-21 set)

**New rules:**

| ID | Level | Conformance | Summary |
|---|---|---|---|
| **TS-22** | MUST | L1 | Test runs MUST support tiered selection. Runner accepts `--tier N` and `--tier N-M` and `--budget T` (time budget; greedy knapsack; tier-0 always included). Mirrors mgf-hrb `_mtest/tiers.py` shape. |
| **TS-23** | MUST | L1 | Every CI test run MUST drive pytest as a **child process** via a supervisor that outlives crashes/hangs. Plain `pytest` invocation is NOT compliant. Supervisor MUST be stdlib-only (no third-party Python deps in the supervisor itself) to avoid bootstrap circularity. |
| **TS-24** | MUST | L1 | Per-test outcomes MUST be written as one JSON object per line to `.gates/last-run/run.jsonl`. Schema MUST follow the federation-shared shape (mirrors `DoctorFinding`); the SCHEMA_VERSION field MUST be set. Importable schema type lives at `mgf.common.testing.TestRecord`. |
| **TS-25** | MUST | L2 | Flaky tests MUST be auto-quarantined. Sliding window of 5 outcomes per test; ≥3 failures → quarantine; ≥3 consecutive passes (under `--quarantine-attempt`) → evict. Quarantined tests skipped by default. State persisted atomically (temp + rename). |
| **TS-26** | MUST | L1 (where applicable) | Projects with a built-binary dependency (Zig broker, Go agent, Rust extension) MUST ship a freshness check: binary mtime ≥ source-tree mtime; stale → **fail** (not skip) with rebuild command in message; missing → **skip** with same command. Dedicated smoke test in `tests/smoke/test_<binary>_freshness.py` MUST fire early. |
| **TS-27** | MUST | L1 | Test failures MUST produce a single-file failure bundle at `.gates/last-run/failures/<safe-test-id>.txt` carrying ALL of: traceback, captured stdout+stderr (last 4 KB), live process state, dmesg tail (best-effort), env fingerprint, last 5 outcomes, chunk wall-time/exit/signal, pointer to project's diagnose-hangs doc. |

**Rationale for the L1 vs L2 split:**
- TS-22..24 + TS-26..27 are L1: they apply to every federation project (the bar consumers MUST clear).
- TS-25 is L2: auto-quarantine is non-trivial to implement and consumers without the supervisor (early-stage CLIs, single-file scripts) can defer it.

**New §13.5 added to TESTING.md** — full taxonomy of markers consistent with the 13-layer model from mgf-hrb (smoke/contract/property/regression/fuzz/conformance/use_case/perf/stress/e2e/unit/functional/integration). Marker → tier mapping documented + the canonical set frozen so the tier rules stay portable.

**New §18 added to TESTING.md** — *"Implementing TS-22..TS-27 — the federation reference is mgf-common/scripts/_mtest/"* — points consumers at the reference impl, summarizes the per-project adaptation knobs (broker process name, build command, marker overrides), shows the `pyproject.toml` snippet for the timeout/faulthandler harness.

**Cross-references:** TS-23 cross-refs PAPER-60 (the excepthook fix that made unraisable warnings detectable inside test runs); TS-26 cross-refs DP-13 (resource lifecycle).

**TESTING.md grows from 1369 lines → ~1700 lines.**

### Phase 2 — `mgf.common.testing` importable module (the public surface)

**New module:** `src/mgf/common/testing/` (a subpackage — `mgf.common.testing` is the import path).

**Files:**
- `__init__.py` — re-exports the public names.
- `_schema.py` — `TestRecord` dataclass (frozen), `Outcome` + `Severity` + `Tier` enums, `SCHEMA_VERSION` constant, JSONL writer helper.
- `_markers.py` — canonical `MARKER_TIER` mapping; helper to compute a test's effective tier from its markers; the `TierBudget` dataclass.
- `_paths.py` — `default_sock_path(prefix, worker=None)` — returns a pytest-xdist-discriminated path (e.g., `/tmp/<prefix>-gw0.sock`); reads `PYTEST_XDIST_WORKER` env var, falls back to `"main"`.
- `_failure_bundle.py` — `FailureBundle` dataclass + serializer; constants for the section markers in the .txt format.

**Public names added to `mgf.common.__all__`:**

```python
# from mgf.common.testing
"TestRecord",              # the JSONL row dataclass
"Outcome",                 # enum: passed | failed | error | skipped | hung | crashed | quarantined
"Severity",                # enum: OK | WARN | FAIL | SKIP
"TestSchemaVersion",       # str constant — current "1.0.0"
"MARKER_TIER",             # dict[str, int] — canonical marker → tier mapping
"effective_tier",          # (markers, override=None) -> int
"TierBudget",              # dataclass — tier name, per-test budget, tier budget
"default_sock_path",       # (prefix, worker=None) -> Path — parallel-safe socket path
"FailureBundle",           # dataclass with serialize()
```

Total: 9 new names. All `stable (v0.40.0)` per AP-11.

**PUBLIC_API.md** gets a new `## mgf.common.testing` section with one row per name.

**Tests:** new `tests/unit/testing/` subdir mirroring the module shape:
- `test_schema.py` — JSONL round-trip, schema_version pin, DoctorFinding parallel
- `test_markers.py` — marker → tier mapping, override behaviour, edge cases (no markers → tier-0 or fail?)
- `test_paths.py` — worker-discriminated paths, PYTEST_XDIST_WORKER env handling, fallback to "main"
- `test_failure_bundle.py` — section ordering pin, serializer round-trip

Estimated ~25 unit tests, ~400 lines of test code, ~250 lines of module code.

**No supervisor logic** lives in this module — that stays in `scripts/_mtest/`. The split is *types vs runtime tool*.

### Phase 3 — Copy-paste the supervisor + ship `mgf-common test` CLI

**Step 3a:** copy `mgf-hrb/scripts/_mtest/` → `mgf-common/scripts/_mtest/` verbatim, with these adaptations:

- `_mtest/diagnostics.py::BROKER_PROCESS_NAME` → `None` (mgf-common has no daemon)
- `_mtest/tiers.py::MARKER_TIER` → import from `mgf.common.testing.MARKER_TIER` (don't duplicate)
- `_mtest/records.py::TestRecord` → import from `mgf.common.testing.TestRecord` (don't duplicate)
- Project-specific markers (`zig`, `broker_*`) removed; mgf-common's existing marker set used
- `tests/_broker.py` → not copied; mgf-common has no broker. Smoke-test for binary freshness (TS-26) skipped — N/A here.

**Step 3b:** copy `mgf-hrb/scripts/mtest` → `mgf-common/scripts/mtest` (venv-bootstrap wrapper).

**Step 3c:** copy `mgf-hrb/docs/testing/DIAGNOSING_HANGS.md` → `mgf-common/docs/testing/DIAGNOSING_HANGS.md` (drop §3 Zig-broker-specific section; keep §0/1/2/4/5/6/7 verbatim).

**Step 3d:** add `mgf-common test` CLI subcommand — thin wrapper that delegates to `scripts/_mtest/__main__.py`. Lives in `src/mgf/common/cli/_test.py`. Same flag surface as the standalone `mtest` script. Discovery is via the canonical CLI: `mgf-common test --help`.

**Step 3e:** add pytest config to `mgf-common/pyproject.toml`:
```toml
[tool.pytest.ini_options]
# ... existing ...
timeout = 30
timeout_method = "thread"
faulthandler_timeout = 60
markers = [
    # ... existing ...
    "smoke: build-broken detector — minimal round-trip. Fast.",
    "contract: wire format + SDK surface pins.",
    "property: invariant-under-all-inputs tests.",
    "regression: pin for a specific past bug. Docstring cites fixing commit.",
    "fuzz: random / generated / hostile input.",
    "conformance: design-promise tests. Release-gated.",
    "use_case: reproduces a consumer scenario from FEEDBACK or recipes.",
    "stress: sustained high-load. Long; nightly only.",
    "perf: latency / throughput budget. Use with PERFORMANCE.md PF-*.",
    "e2e: end-to-end against a live external system. Long; opt-in.",
]
# Default suite excludes the slow/expensive layers (kept by mtest --tier 3 or explicit -m)
addopts = "... -m 'not stress and not perf and not e2e and not conformance'"

[project.optional-dependencies]
dev = [
    # ... existing ...
    "pytest-timeout>=2.3",
]
```

**Step 3f:** add `.gates/` to `.gitignore`.

**Step 3g:** new `tests/smoke/test_mtest_self_check.py` (~30 lines) — runs `mtest --self-check` as a smoke test (TS-26 enforcement target).

**Step 3h:** `mgf-common catch-up` gains a TS-22..27 audit (Phase 3.5, separate commit):
- Verifies `scripts/_mtest/` exists OR project explicitly opted out via `MGF_STANDARDS_CONFORMANCE.md` carve-out (small consumers, single-file CLIs).
- Verifies `pyproject.toml` has the timeout/faulthandler config.
- Verifies the markers section is registered.
- Verifies `.gates/` is gitignored (prevents accidental commit of run state).

---

## §4 — Extraction trigger (codified)

The maintainer's discipline: *"revisit extraction after ≥2 siblings adopt the spec via copy-paste."*

**After Phase 3 lands:** mgf-common is **sibling #1**.

**The next adoption — sibling #2** — triggers extraction work. The trigger is mechanical: when a second federation sibling (any of mgf-fastapi, mgf-django, mgf-http, mgf-alembic, mgf-apiprobe, mgf-sqlalchemy, mgf-vm-*, future siblings) copy-pastes `scripts/_mtest/` into their repo, the `mgf-common catch-up` audit detects it (greps for `scripts/_mtest/__init__.py` across federation siblings in a federation-wide audit table) and surfaces a finding: *"≥2 siblings now ship the supervisor by copy-paste — time to extract."*

**Extraction work (when triggered):**

1. New repo: `magogi-admin/mgf-test-supervisor` (NM-03 hyphen slug).
2. New PyPI package: `mgf-test-supervisor`; console script `mtest`.
3. Source from `mgf-common/scripts/_mtest/` (most-recently-evolved + most-battle-tested copy).
4. Adopters drop their `scripts/_mtest/` directory, add `mgf-test-supervisor` to `[project.optional-dependencies].dev`, the `mgf-common test` CLI in mgf-common becomes a re-export of the sibling's `mtest` entry point.
5. `mgf.common.testing` importable module stays where it is — it's the library-shaped surface and was always meant to graduate independently of the supervisor.

**De-risk plan for the extraction:** the supervisor's JSONL schema + the `mgf.common.testing` types are already public + versioned before extraction. The sibling library can ship `mgf-test-supervisor` v1.0.0 from day one with stability guarantees by inheriting both from `mgf.common.testing.SCHEMA_VERSION`.

**Documented in:** TESTING.md §18 (the "Implementing TS-22..27" section) carries the trigger language so future maintainers can recognize when it's time without re-reading this proposal.

---

## §5 — What this proposal does NOT do

A few deliberate non-goals worth surfacing so they don't slip in later:

- **No new dashboard.** The mgf-hrb suggestion #6 ("Magogi Foundation dashboard consuming run.jsonl from every federation project") is a downstream consumer of the schema, not part of the supervisor itself. Defer until 2-3 siblings have JSONL flowing.
- **No pytest-xdist parallel runs as part of Phase 3.** mgf-hrb suggestion #5 says the infrastructure is one knob away (the `default_sock_path` helper); BUT extracting parallel support means changing the supervisor's chunking logic. Defer to Phase 4 (post-extraction) — adding parallel mode AFTER extraction is cleaner because both adopters can collaborate on the right shape.
- **No `--report --json` machine-readable summary mode.** mgf-hrb suggestion #7. Defer — the per-test `run.jsonl` is already machine-readable; `--report` is a human-readable convenience. Add when a downstream tool actually needs it.
- **No new sibling library today.** Per §2.2 above.
- **No changes to existing TS-01..TS-21 rules.** TS-22..27 are ADDITIVE; nothing in the existing rule set conflicts.
- **No `mgf-common.pytest_plugin` changes** in Phases 1-3. The plugin's 5 existing fixtures + `decode_json_logs` helper are orthogonal (test-time fixtures, not test-run-time supervisor surface). Future fixtures could land there but not as part of this proposal.

---

## §6 — Risks + mitigations

| Risk | Mitigation |
|---|---|
| **Bloating mgf-common's CLI surface** (already 12 subcommands; adding `test` makes 13). | `mgf-common test` is a thin shim — heavy lifting in `scripts/_mtest/`, not in `src/mgf/common/cli/`. CLI help text + `mgf-common standards` updates keep discoverability. The 13th subcommand is a clear sibling of `mgf-common bench` (PERFORMANCE.md's tool). |
| **Coupling adoption speed to mgf-common release cadence.** | The SPEC propagates immediately on TS-22..27 ship; consumers can implement TS-22..27 with their own supervisor or by copy-pasting from `mgf-common/scripts/_mtest/` whenever they want — they don't need to wait for a mgf-common release. The IMPLEMENTATION coupling matters only when extraction lands. |
| **Drift between copy-paste copies.** | The maintainer's stated discipline: *"each project owning its own copy means a supervisor change in one project doesn't auto-break others; the spec (TS-22..27) holds the line."* The spec is the contract; implementations may drift slightly. When drift becomes painful, extraction trigger fires. |
| **The 4-tier model doesn't fit every project.** | Markers + tier overrides are configurable; `MARKER_TIER` is just a default. Consumers override via their pytest config. Worst case: a consumer ships with all tests as tier-1 and `mtest --tier 0-3` runs everything. |
| **mgf-common's own test suite slows down** (1675 tests today; supervisor adds chunking overhead). | Chunking is configurable (`--chunk-size`, default 50); overhead is ~1s per chunk for fork+pytest-startup. With 1675 tests in chunks of 50, that's ~33 chunks × 1s = 33s of overhead. Existing `pytest -q` finishes in 18s, so the supervised run would be ~50s — acceptable for the rich diagnostics, faster than 1675 separate pytest invocations would be. Local dev workflow unchanged (developers still run plain `pytest tests/X` for tight loops). |
| **CI integration friction** (Woodpecker / GitHub Actions). | `mtest` returns standard exit codes (0=clean, non-zero=fail); CI configs are unchanged beyond swapping `pytest` → `mtest` in the test step. WORKFLOWS_PATTERNS.md gets a new canonical snippet. |
| **mgf-common adoption reveals the abstraction is wrong.** | Best case: we catch it BEFORE extraction (the point of the discipline). Either we fix the abstraction in-place (copy-paste in mgf-common gets revised, mgf-hrb syncs), or we revert TS-22..27 (deprecation cycle) and re-design. Cost is contained because no published package depends on it yet. |

---

## §7 — Open questions for maintainer ack

Before I implement, please decide:

1. **Sibling naming when extracted.** mgf-hrb's `MGF_COMMON_ADOPTION_MESSAGE.md` proposed **`mgf-test-supervisor`**. Alternatives: `mgf-mtest` (preserves CLI name), `mgf-gates` (matches `.gates/` dir name), `mgf-testkit`. **My recommendation: `mgf-test-supervisor`** — verbose but unambiguous, matches what's already documented.
2. **`mgf.common.testing` vs `mgf.common.test`** for the importable module path. `testing` matches stdlib (`unittest` is in `test_*` files but the helper module is conventionally `*.testing`). **My recommendation: `mgf.common.testing`** — explicit, descriptive, no clash with pytest's test-file discovery patterns.
3. **TS-25 (auto-quarantine) as L1 or L2.** Currently proposed L2 (every L2 project, not every L1). Argument for L1: quarantine state is structurally so simple (~217 lines) that even small consumers can implement it. Argument for L2: small consumers without CI shouldn't be forced into quarantine policy. **My recommendation: L2** — gives small CLI consumers an opt-out.
4. **Marker taxonomy reconciliation.** mgf-common's existing TS-19/TS-20 marker set is `smoke / unit / integration / e2e / slow`. mgf-hrb's set is `smoke / contract / property / regression / fuzz / conformance / use_case / perf / stress / e2e`. **My recommendation:** mgf-common adopts mgf-hrb's superset; the existing `slow` marker becomes a no-op alias (deprecated, removed v2.0); `unit / integration` are implicit (no marker = unit) per pytest convention.
5. **Where does `DIAGNOSING_HANGS.md` live?** Options: (a) `mgf-common/docs/testing/DIAGNOSING_HANGS.md` (new subdir under docs/), (b) `mgf-common/docs/standards/DIAGNOSING_HANGS.md` (sits as a peer of the standards), (c) referenced-only from TESTING.md, lives in mgf-hrb (consumers go cross-repo for it). **My recommendation: (a)** — `docs/testing/` is a new home for testing-tool docs that aren't themselves standards. Sets up for a future `docs/testing/TAXONOMY.md` etc. if needed.

---

## §8 — Estimated effort

| Phase | Description | LOC delta | Files touched | Estimated time |
|---|---|---|---|---|
| **1** | TS-22..27 in TESTING.md + §13.5 marker reconciliation + §18 reference-impl section | +330 (docs only) | `TESTING.md`, `docs/standards/README.md` (index) | ~2 hr |
| **2** | `mgf.common.testing` module + 25 unit tests + PUBLIC_API.md rows + 9 names in `__all__` | +250 src / +400 tests / +60 docs | 4 new src files, 4 new test files, `__init__.py`, `PUBLIC_API.md`, `PUBLIC_API.json` | ~3 hr |
| **3** | Copy-paste `scripts/_mtest/` + adapt + new `mgf-common test` CLI subcommand + pytest config + .gitignore + smoke test + DIAGNOSING_HANGS.md | +2,300 (mostly copied) | ~15 files | ~3 hr |
| Total | All three phases — single PR or three sequential commits | +3,300 LOC | ~25 files | ~8 hr |

Plus ~1 hr per follow-up:
- CHANGELOG.md `[Unreleased]` entry
- Update `mgf-hrb`'s `MGF_COMMON_ADOPTION_MESSAGE.md` to reference the landed shape
- Memory entry recording the decision

---

## §9 — Decision needed

Please ack:

- [ ] Recommendation in §0 (phased: in-mgf-common now, sibling later) — accept / reject / modify
- [ ] Sibling naming `mgf-test-supervisor` (question 1) — accept / pick alternative
- [ ] Module path `mgf.common.testing` (question 2) — accept / pick alternative
- [ ] TS-25 as L2 (question 3) — accept / promote to L1
- [ ] Marker taxonomy reconciliation (question 4) — accept / negotiate
- [ ] DIAGNOSING_HANGS.md location (question 5) — accept / pick alternative
- [ ] Phase ordering (standards first → importable surface → supervisor copy) — accept / re-sequence
- [ ] Effort estimate plausible / ok to proceed — accept / split into smaller PRs

Once acked, I'll execute the three phases in order, commit each as a logical unit, push, and report back. mgf-hrb's existing `MGF_COMMON_ADOPTION_MESSAGE.md` is then archived (its goal is achieved).
