# Changelog

All notable changes to `mgf-test-supervisor` will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Pre-v1.0 discipline (see `mgf-standard/docs/standards/RELEASE.md` AP-03): one minor at a time; breaking changes allowed pre-1.0; consumers walk pins forward deliberately.


## [Unreleased]

### Added
- **Failure detail is echoed to stdout on a red run** (PAPER-08). The
  one-line `[mtest] FAIL: …` summary told you THAT the gate was red; the
  detail lived only in `.gates/last-run/failures/*.txt`, which CI never
  uploads — so a pipeline log physically could not say WHICH test failed
  or why (mgf-hrb's main sat red for weeks on exactly this). `mtest` now
  prints, after the summary, every failing node-id + outcome + summary
  line + bundle path, and the tail of each failure's captured
  traceback/assertion. Bounded: detail expanded for the first 20 failures
  (`PRINT_FAILURES_MAX_TESTS`), tail-truncated to the last 40 lines each
  (`PRINT_FAILURES_TAIL_LINES`); node-ids are NEVER withheld. Default is
  automatic — on when stdout is not a TTY or `CI=` is truthy, off for an
  interactive human; `--print-failures` forces, `--no-print-failures`
  suppresses. Failures inherited from an interrupted run (`--resume`) are
  read back out of their bundle, so they print too.

### Fixed
- **The "details:" pointer lines now name the REAL gates dir.** They
  hardcoded `.gates/…`, which is a lie under `MGF_GATES_DIR=…` — the very
  configuration CI uses. `summary.md`'s per-failure links had the same bug.

## [0.1.20] - 2026-07-14

### Fixed
- **A red run no longer turns GREEN on re-run.** `is_quarantined` returned bare store
  membership, and the selector built `quarantined_ids` from `store.all().keys()` — both counted
  the *tracking* entry that `update_outcome` inserts on the FIRST failure. So a test that failed
  once was skipped on the next run and the suite exited 0; "just run it again" silently buried a
  100%-reproducible failure. Both now consult the `quarantined_at` policy stamp.
- **A deterministically broken test is never quarantined-to-green.** TS-25 quarantines *flaky*
  tests, and a test that has never passed in its window is BROKEN, not flaky. Quarantine now
  requires ≥1 pass in the window; an all-failing test stays RED, running every time, until it
  passes or is fixed. (Verified end-to-end: a broken test exits 1 across repeated runs; a
  genuinely flaky test still quarantines.)

### Note
This release also ships the PAPER-05/06/08 fixes that had been sitting on `main` since v0.1.19
without a release — see the entries below. A fix that isn't released isn't shipped.

## [0.1.19] - 2026-07-05 — Fix: IPv6-ish parametrize ids no longer false-red the gate

### Fixed
- **`_node_id_to_classkey` no longer shreds node-ids whose parametrise suffix
  contains `::`** (IPv6 literals — `test_v6[::1]`, `test_bind[2001:db8::1]`).
  The naive `str.split("::")` treated the `::` inside `[param]` as a node-id
  separator, so the computed `(classname, name)` key never matched the JUnit
  testcase and the coverage gate false-reported the test as missing. Split now
  runs on structural `::` only (bracket-depth 0) via the new `_split_nodeid`
  helper; `[param]` contents are preserved verbatim. Regression tests cover
  module-level, class-based, zone-id, and nested-bracket cases. No public API
  change (both functions are private).

### Changed
- **Marker collection is ONE pytest spawn** (PLAN §2.3). `collect_tests` injects
  a temp-dir marker-dump plugin (`pytest_collection_finish` → JSON) and returns
  node-ids + REAL markers from a single `--collect-only -q --no-header` run;
  `collect_test_markers` is now a derivation over it (no extra spawns). Measured
  with `time.monotonic`: 1+13 spawns → 1 spawn, 4.46s → 0.31s on this repo
  (121 tests) and 8.38s → 0.58s on mgf-common (1.6k tests) — ~14x. Side
  benefit: collection now uses pytest's own item list, so parametrized ids the
  old stdout-regex missed (e.g. params containing `/`) are collected again
  (mgf-common: 1623 → 1659 tests seen). Fallback to the old stdout parse
  (markers empty) if the plugin can't load — degraded, never blocked. This also
  removes the dead always-`()` marker tuple in `collect_tests`.
- **`--require-nonempty` is the DEFAULT for explicit `--tier` runs** (PAPER-95
  completed): a tiered gate that selects zero tests exits 2. New `--allow-empty`
  opts out; `--require-nonempty` still forces the behaviour for un-tiered runs.
- **Quarantine honesty (PLAN §1.4).** Quarantined selections now emit one
  `quarantined` TestRecord (severity **WARN**, code `QUARANTINED`, with the last
  failure summary and remediation) into `run.jsonl` — the dead
  `Outcome.QUARANTINED` lives. `severity_for_outcome("quarantined")` is now
  `WARN` (was `SKIP`), unified with `mgf.common.testing.DEFAULT_SEVERITY`; the
  terminal summary counts quarantined separately from skipped; new
  `--quarantine-report` prints a compact table after the run.
- **Failure bundles emit through the canonical SECTION_ORDER** (TS-27).
  `records.py` vendors `SECTION_ORDER` + `SECTION_HEADER_FMT` byte-for-byte from
  `mgf.common.testing` (TS-23 forbids the runtime import; the alignment tests
  byte-pin the copies) and the writer emits every canonical `=== SECTION ===`
  in order — the previous 19 ad-hoc lowercase sections matched zero headers of
  the schema downstream tooling greps for. Perf attribution appends as an extra
  section after the canonical set.
- **Hang heuristic hardened** (false-red reduction). The internal
  pytest-timeout classification now requires wall >= **0.95x** the per-test
  timeout (was 0.9) AND rc != 0 AND **no output in the final `HANG_SILENCE_S`
  (5s) window**, tracked by a new streaming output drain (burst-tolerant:
  pytest-timeout's stack dump right before `os._exit` doesn't launder the
  silence). Busy-but-slow chunks near the timeout no longer mislabel as hung.
  The drain also preserves partial output through the SIGTERM→SIGKILL ladder
  (previously discarded on a wedged final `communicate`).
- **TierBudget unified to mgf-common's shape** (frozen dataclass; fields
  `name` / `per_test_seconds` / `suite_seconds` / `description` — the
  NamedTuple with `typical_test_s` / `total_budget_s` / `intent` is gone).
  `tier_for_markers` gained `override` / `mapping` args and now matches
  `mgf.common.testing.effective_tier` semantics exactly (ValueError on
  out-of-range override). Alignment tests pin field names, values, the
  severity MAPPING, SECTION_ORDER, and the tier-function equivalence — with a
  feature-detect module skip against a pre-Wave-2 mgf-common (release-dance
  guard; arms automatically once mgf-common >= 0.50 is installed).
- **Auto-tier starts from the real marker tier.**
  `DurationStore.auto_tier_overrides(base_tiers)` takes the marker-derived
  floor per test (the CLI passes it from the one-spawn collection) — an
  `integration` test can now be promoted to tier-3; the old hardcoded base-1
  capped everything at tier-2.

### Removed
- **The `slow` marker completed its deprecation cycle** (founder Wave-2 tier
  sitting): removed from `MARKER_TIER`, this repo's registered markers, the
  default-suite exclusion, README, and the CI step (`slow-suite` →
  `e2e-suite`). The `--self-check` smoke test is now marked `e2e` (a ~32s
  end-to-end run was never honest tier-0 smoke). Migrate residual
  `@pytest.mark.slow` to the specific tier-3 marker (perf / stress / e2e) or
  let auto-tier promote from observed durations.

### Fixed
- `docs/testing/SUPERVISOR_DESIGN.md` now EXISTS (the `--help` text referenced
  a ghost); short architecture doc distilled from the module docstrings.
- DoctorFinding provenance in `records.py` names the one real parent
  (`mgf.hrb.diagnostics.DoctorFinding`) and the phantom DEFERRED severity
  mention is gone (DoctorFinding's DEFERRED has no test-outcome meaning; SKIP
  is the test-specific fourth severity).
- README install text: devpi pin replaces the retired `git+ssh://` path;
  tier table corrected (`integration` is tier-2 — a pre-PAPER-62 leftover said
  tier-1); `TestRecord` gained the `__test__ = False` collection guard
  (parity with mgf.common.testing).

## [0.1.17] - 2026-06-25

### Changed
- Widen the `mgf-common` pin to `<0.49` — adopt **0.48**. Cohort pin-walk for the 0.48 minor; no code changes.

## [0.1.16] - 2026-06-17

### Added
- The supervisor now exports `MGF_TEST_SUPERVISOR_ACTIVE=1` into every test
  subprocess's environment. A self-referential meta-test — one that itself
  invokes `mtest` (e.g. a `--self-check` smoke test) — can detect this and
  `pytest.skip` rather than nest mtest-in-mtest, which over-runs the per-test
  wall-clock budget and gets reported as a false `hung`. Plain `pytest` runs
  never set it, so the meta-test still runs there.
- `RunConfig.per_test_timeout_s` (default = `DEFAULT_PER_TEST_TIMEOUT_S`, 30s)
  makes the outer per-test wall-clock budget overridable.

### Changed
- `--self-check`'s hang scenario now uses a small per-test budget so the
  outer wall-clock fires in ~12s instead of ~40s — same `hung` classification,
  just cheap. The whole self-check drops from ~40s to ~16s.

## [0.1.15] - 2026-06-16

### Changed
- Widened the `mgf-common` pin to `<0.48` — mgf-common 0.47 is the pure-library release (the end-user CLI moved to `mgf-fed[cli]`); the importable API is unchanged.

## [0.1.14] - 2026-06-14

### Fixed
- **`--self-check` no longer takes the shared `.gates/.lock`.** It runs each scenario in its own
  tmp `.gates` (never the shared stores), so parallel CI self-checks — the supervisor smoke test
  across 3 Python-version steps + the dedicated supervisor-self-check step — no longer fail with
  "another run holds .gates/.lock". The PAPER-95 lock now guards only the real run.
- **New `MGF_GATES_DIR` env override** to point a run at an isolated `.gates/` dir (lets concurrent
  CI steps avoid lock contention entirely).

## [0.1.13] — 2026-06-07 — rich --version (OP-01)

### Changed

- `mtest --version` / `mgf-test-supervisor --version` now returns a rich version:
  package version + git commit (best-effort) + Python version/impl + OS-arch +
  interpreter path (OP-01). Lazy — git is only invoked on `--version`.

## [0.1.12] — 2026-06-07 — public API surface (depth-audit AP cluster)

### Added

- `__all__` on all 8 public modules + the package root now re-exports the full
  public surface (`from mgf.test_supervisor import …`) — AP-01.
- `PUBLIC_API.md` (every public name + stability tier + description) + machine
  companion `PUBLIC_API.json` + `tools/generate_public_api_json.py` + CI pin
  tests — AP-10 / AP-11 / DOC-09.

### Changed

- `__init__.py` now carries `from __future__ import annotations` (AP-08).

## [0.1.11] — 2026-06-06 — federation cohort: mgf-common 0.46 walk

### Changed

Walk the `mgf-common` pin ceiling `<0.46 → <0.47` to accept the
library-API-neutral `mgf-common 0.46.0` (CLI-only release: new
`federation`/`design` commands + a `doctor --standards` gate; no library
surface change).

---

## [0.1.10] — 2026-06-04 — federation cohort: mgf-common 0.45 walk

### Changed

PATCH: pin widened to `mgf-common>=0.41,<0.46` to accept the mgf-common 0.45
line (the Codeberg package-distribution cutover — `mgf-standard`
`PACKAGE_DISTRIBUTION.md`, rules PKG-01..12). No API change. mgf-* already
resolve from the Codeberg index (`codeberg-mgf`, PKG-06).

---

## [0.1.9] — 2026-06-01 — federation cohort: adopt mgf-common 0.44

### Changed

PATCH: pin widened to `mgf-common>=0.41,<0.45` to accept the mgf-common 0.44 line
(which extracted the standards corpus + federation contract to the
`mgf-standard` sibling; pulled in transitively). `FEDERATION.md` stub +
`.claude/commands/sync.md` repointed at `mgf-standard`. No API change.

---

## [0.1.8] — per-test perf attribution (TS-28)

### Added

- **`--perf` flag** — captures per-test perf attribution. Sets `MGF_PERF=1` +
  `MGF_PERF_DIR` in the pytest subprocess so mgf-common's plugin writes a
  per-test JSON artifact (`cProfile` function breakdown + `perf_span`
  sections); the supervisor reads them back **out-of-process** (it never
  imports mgf-common — TS-23 holds) via the new stdlib-only
  `mgf.test_supervisor.perf` module.
- **Perf surfaced in three places:** the `TestRecord.perf` field of
  `run.jsonl`, a `perf attribution` section in each TS-27 failure bundle, and
  a run-level `.gates/last-run/perf-summary.md` (slowest tests by wall-time +
  hottest functions by self-time summed across the run).

### Changed

- **Record schema → `1.1.0`** — adds the nullable `perf` field (aligned with
  `mgf.common.testing.TestRecord`; the two-sided alignment tests pin it).

### Fixed / accuracy

- **`--perf` runs no longer pollute the duration baseline.** cProfile inflates
  measured wall-time, so a perf run is treated as diagnostic — its timings are
  not recorded into `durations.json`, keeping the budget selector + auto-tier
  honest. (Outcome tracking + quarantine still update, since perf doesn't
  affect pass/fail.)

---

## [0.1.7] — 2026-05-28 — federation cohort publish (mgf-common 0.42.x)

Federation cohort wave: mgf-test-supervisor accepts **mgf-common 0.42.x** (pin widened to
`>=0.41,<0.43`) and ships to PyPI, carrying the previously-unpublished 0.1.6 work.

---

## [0.1.6] — 2026-05-28 — class-based test reconciliation fix + adoption-safety hardening

### Fixed

- **Class-based tests were silently quarantined.** JUnit reconciliation
  rebuilt node-ids from the dotted `classname` + an absent `file` attribute,
  so class-based tests never matched their collected node-id, were flagged
  `MISSING_FROM_XML`, and auto-quarantined — a passing class suite reported as
  not-run (false green). Now keyed on the canonical `(classname, name)` pair.
  Verified end-to-end on a 54-test class-based suite. (PAPER-96)

### Added

- Fail-loud on an empty tier selection (`mtest --tier N` matching zero tests
  warns + exits non-zero under `--require-nonempty`) — closes the silent
  green-gate adoption cliff (PAPER-95).
- Advisory `flock` on `.gates/` so concurrent runs can't corrupt shared state.
- A coverage gate on the tool itself (fail_under ratchet), a fast tier-0 smoke
  set, a tier-subset property test, and a live (not mocked) crash-detection test.
- `USERS.md` (DOC-16).

### Notes

- Also cleared the tool's own pre-existing ruff/mypy debt so it passes its
  four gates under current toolchains. No public CLI surface change.

---

## [0.1.5] — 2026-05-27 — declare L2 conformance + scaffold tracker

Federation L2-floor walk-up. Every sibling must declare a
conformance level per CFM-02; the tracker (per DOC-15) is the
rolling per-rule audit ledger. mgf-test-supervisor previously
had neither.

### Added

- `docs/inprogress/MGF_STANDARDS_CONFORMANCE.md` — initial audit
  scaffold. Declares **L2 — Standard**. Three documented declines
  (LG / CF / OB runtime adoption), all driven by the TS-23
  stdlib-only constraint that defines the supervisor's shape:
  the supervisor must drive pytest in any consumer's environment
  without dragging optional deps, so adopting `mgf.common`
  runtime surfaces would defeat the dev-only-extras boundary.
  Output-side conformance (TestRecord JSONL = LG-04 structured
  records; deterministic test_id = LG-05 trace correlation;
  TestRecord JSONL = OB output contract) is preserved.

- `README.md` top-of-file declaration line pointing at the
  tracker, per the convention in
  `mgf-standard/docs/standards/README.md` §"Conformance levels for
  projects."

### Federation context

mgf-common 0.41.6 surfaced a federation conformance walk-up:
all production siblings must hold L2 minimum. mgf-test-supervisor
was one of two siblings missing a tracker (the other is mgf-vm,
which gets its own audit pass next). This release fills that gap
without changing any code — the structural conformance was
already there by construction, just unaudited on paper.

No code change in this release. The version bump is the
mechanism that gets the new doc artifacts onto PyPI's source
tarball + into Codeberg under a release tag (REL-01).

## [0.1.4] — 2026-05-27 — MARKER_TIER additions: integration + slow (PAPER-62)

Mirrors the additions in `mgf-common` 0.41.6. The federation
13-marker taxonomy declared `integration` and `slow` in every
sibling's `[tool.pytest.ini_options].markers` block, but
`MARKER_TIER` only carried 11 of the 13 — tests using either
marker silently fell through to `DEFAULT_TIER = 1` via
`effective_tier(...)`, getting a tier-1 wall-clock budget
regardless of what they actually do.

### Added

- `integration` → tier-2 (cross-module integration, may launch
  subprocesses; fits the 2s per-test budget).
- `slow` → tier-3 (long-signal tier alongside `perf` / `stress` /
  `e2e`).

Pure additive change — consumers using either marker get their
tests classified into the correct tier rather than tier-1, which
means more honest budget accounting and more accurate auto-tier
bump decisions. No code paths break; tests that were silently
mis-classified into tier-1 now appear in tier-2 or tier-3 as
appropriate.

Released in lockstep with `mgf-common` 0.41.6 so the alignment
pin (`tests/unit/testing/test_supervisor_alignment.py
::TestMarkerTierAligned`) stays green on both sides.

## [0.1.3] — 2026-05-26 — version-string sync fix (was bug since 0.1.0)

`src/mgf/test_supervisor/__init__.py::__version__` was the literal
string `"0.1.0"` from the initial extraction commit on through
0.1.1 + 0.1.2 — only `pyproject.toml` got bumped per release. The
wheel's METADATA said the right version (driven by pyproject) but
runtime introspection (`mgf.test_supervisor.__version__`,
`mtest --version`) reported 0.1.0 for all three releases.

Caught during PyPI publish smoke test for 0.1.2:
`pip install mgf-test-supervisor==0.1.2 && python -c "import
mgf.test_supervisor; print(mgf.test_supervisor.__version__)"`
printed `0.1.0`.

### Changed

- `src/mgf/test_supervisor/__init__.py`: `__version__ = "0.1.0"`
  → `__version__ = "0.1.3"`. Reflects the actual release.
- `pyproject.toml`: version 0.1.2 → 0.1.3.
- New test `tests/unit/test_version_in_sync.py` pins the
  pyproject version + `__version__` constant to the same value.
  Mirrors mgf-common's equivalent test
  (`tests/unit/test_version_in_sync.py`) that catches exactly
  this class of skew at CI time. Future bumps fail loudly if
  only one side gets bumped.

### Note for consumers

Functionally identical to 0.1.2 — only the version string is
fixed. `pip install -U mgf-test-supervisor` is the cleanest
upgrade; no API changes.

## [0.1.2] — 2026-05-26 — self-check surfaces pytest stderr on CRASHED (PAPER-77 part 3)

mgf-common's PAPER-77 (hrb-app sibling-#8 follow-up) identified the
worst first-contact failure mode for new adopters: wire the TS-23
timeout config in pyproject without `pytest-timeout` installed,
get four CRASHED outcomes from `--self-check` with zero hint of
the missing-plugin root cause. The fix is the supervisor's own
self-check showing what pytest actually said.

### Changed

- `_run_canned` (`src/mgf/test_supervisor/self_check.py`) now
  prints diagnostic detail on outcome mismatch ALWAYS, not just
  under `--verbose`. First-contact adopters won't know to opt
  into verbose; the cost of a few extra lines on a failed
  self-check is trivial compared to the cost of an opaque FAIL.
- When the actual outcome is `crashed` or `error` (the
  categories that surface pytest infrastructure failures like
  unknown-config-option, INTERNALERROR, import-time exceptions),
  the captured pytest stderr (last 20 lines) is dumped under
  the FAIL line.
- When the captured stderr contains both `Unknown config
  option` and `timeout`, an explicit "this is the PAPER-77
  footgun — add `pytest-timeout>=2.3,<3` to dev extras" hint
  is printed inline. Future-similar footguns can be added the
  same way.
- The "no JSONL records written" path (pytest failed to start
  at all) now ALWAYS surfaces a clear diagnostic, not just
  under `--verbose`.

### Added

- 3 unit tests in `tests/unit/test_self_check_visibility.py`
  pinning the new behavior. Crashed outcomes surface stderr +
  PAPER-77 hint; non-crashed mismatches surface outcome diff
  only (no stderr noise); empty-JSONL surfaces "pytest may have
  failed before running."

### Federation context

- Companion to mgf-common commit `79a52d3` (PAPER-77 part 2 —
  catch-up's TS-22..27 finding lists `pytest-timeout` as a
  required dev dep + cross-refs the PAPER-77 footgun).
- Part (1) of PAPER-77 (declare `pytest-timeout` as a runtime
  transitive dep of `mgf-test-supervisor`) NOT adopted — the
  stdlib-only invariant per TS-23 is more important than the
  "single install command does everything" goal. The README +
  catch-up audit cover the "you also need pytest-timeout"
  signal; this commit makes the silent failure mode loud at
  failure time.

## [0.1.1] — 2026-05-26 — daemon-name env-var override

Sibling-#8 (mgf-hrb — the daemon-bearing originator of the
pre-extraction supervisor) was about to adopt the library but
would silently lose its `daemon_state()` failure-bundle line:
the extraction hard-coded `DAEMON_PROCESS_NAME = None` since
mgf-common (the extraction test-bed) has no daemon, and forking
the library to flip the constant defeats the whole point of
having an extracted library.

### Added

- `MGF_TEST_SUPERVISOR_DAEMON_PROCESS_NAME` env var — adopter
  projects with a daemon set this in `.envrc` / `conftest.py` /
  CI step and the supervisor library stays unchanged. Symmetric
  shape with the existing `MGF_TEST_SUPERVISOR_REPO_ROOT` pattern.
- `_daemon_process_name()` helper that resolves env-var → module
  constant → None. Read at call time so adapter projects can set
  the var in a session-startup hook and the supervisor picks it
  up without re-import gymnastics.
- 7 unit tests at `tests/unit/test_daemon_process_name.py`
  pinning every cell of the (env, constant) truth table.

### Changed

- `daemon_state()` calls the resolver instead of reading
  `DAEMON_PROCESS_NAME` directly. Daemon-less projects keep the
  silent-no-op behaviour (empty string return); daemon-bearing
  projects get the `pgrep` + wchan + thread-count snapshot per
  matching process.
- Status-line heading "live broker processes:" → "live `<name>`
  processes:" so the bundle names the daemon being probed.

### Why this matters

mgf-hrb migrating cleanly without a daemon-state regression
preserves the diagnostic that paid for itself the very first
time we shipped it (the stale-broker-binary trap from
2026-05-25 was diagnosed via `wchan = unix_stream_data_wait` on
the live broker). Sibling-#8 + every future daemon-bearing
worker (mgf-hrb-usb-priv, ...) inherits the same diagnostic with
zero per-project library fork.

## [0.1.0] — 2026-05-26 — initial extraction from mgf-common

Extracted from `mgf-common/scripts/_mtest/` after all 7 federation siblings adopted it via copy-paste (mgf-common + mgf-fastapi + mgf-django + mgf-sqlalchemy + mgf-alembic + mgf-http + mgf-apiprobe).

### Added

- `mgf.test_supervisor` package (stdlib-only, per TS-23).
- Console scripts: `mgf-test-supervisor` (canonical) + `mtest` (alias).
- `--self-check` mode — verifies pass / fail / crash / hang detection with canned scenarios in tmpdirs. Exit code 4 if the supervisor can't tell those four states apart from each other.
- `--tier N` / `--tier N-M` selection (federation tier system, TS-25).
- `--budget Xs|Xm|Xh` greedy-knapsack selection biased to tier-0 + recently-failing tests.
- `--resume` to continue an interrupted run from `.gates/state.json`.
- `--report` / `--quarantine` for last-run summary + quarantine inspection.
- 3-of-5-fail-in / 3-consec-pass-out quarantine.
- Per-failure diagnostic bundles (`.gates/last-run/failures/<test_id>.txt`).
- JSONL outcomes (`run.jsonl`); schema-aligned with `mgf.common.testing.TestRecord`.
- Repo-root discovery walks up from CWD looking for `pyproject.toml`. Override with `MGF_TEST_SUPERVISOR_REPO_ROOT`. Missing pyproject is a SystemExit with an actionable message (NOT a silent fallback).

### Pre-extraction hardening (lessons baked in)

- **L6 (sibling-#7 mgf-apiprobe).** Supervisor uses `-p no:pytest_cov` (pytest's standard "disable plugin if loaded; silent no-op if not" pattern) instead of the pytest-cov-specific `--no-cov` flag. Works whether or not pytest-cov is installed in the consumer's env. Pinned by `tests/unit/test_plugin_hygiene.py`.
- **Collection-failure visibility.** When `collect_tests()` returns empty AND pytest exited non-zero, the supervisor surfaces pytest's stderr immediately — no more "no tests" without context. Pinned by `tests/unit/test_plugin_hygiene.py::TestCollectionFailureSurfaces`.
- **Repo-root discovery contract** pinned by `tests/unit/test_repo_root_discovery.py` (walks-up succeeds / env-var overrides / missing-pyproject is actionable SystemExit).

### Federation context

- Federation standards TS-22..TS-27 live in `mgf-standard/docs/standards/TESTING.md` Rules + §13.11 + §19.
- Consumer-readable JSONL schema lives in `mgf.common.testing` (13 stable `v0.40.0` public names).
- Adoption recipe: `mgf-common/docs/recipes/test-supervisor-adoption.md`.
- Rollout history: `mgf-common/docs/inprogress/SUPERVISOR_ROLLOUT.md`.
