# Public API — `mgf-common`

> ⚠️ **This file documents `master` HEAD.** Names listed here may
> be unreleased. To learn what your installed wheel actually
> ships, run `pip show mgf-common` and compare against the
> `__version__` field at the top of `PUBLIC_API.json`. The git
> tag matching the published version is authoritative; this
> file's tip can be ahead of it during a release window. Closes
> FEEDBACK PAPER-12.

This document is the **contract list** for `mgf-common`. Every name
listed below is a public name that consumers MAY depend on.

The contract terms are defined in
[`docs/standards/API_DESIGN.md`](docs/standards/API_DESIGN.md). In
short:

- **Stability tiers:**
  - `experimental` — MAY change shape in any MINOR release
    (the 0.x window keeps everything experimental in practice).
  - `stable` — follows the deprecation cycle; removal only in MAJOR.
  - `deprecated` — slated for removal; emits `DeprecationWarning`.
- **Names not listed below are private.** Anything starting with
  `_` (underscore-prefixed module names like `_identity.py`) is
  internal and MAY change without notice.
- **The 0.x window applies.** Per [SemVer](https://semver.org/) 0.x
  semantics, MINOR releases MAY break the public API. Pin tightly:
  `mgf-common = ">=0.X.0,<0.Y"`. When the public API stabilises (post-1.0), the standard
  MAJOR / MINOR / PATCH discipline applies.

This file MUST stay in sync with the `__all__` of every public
package — see the pin test pattern in
[`docs/standards/API_DESIGN.md`](docs/standards/API_DESIGN.md) §2.4.

---

## `mgf.common`

Top-level identity + version surface. Three names + the version
attribute.

| Name | Tier | Description |
|---|---|---|
| `bootstrap(app_name: str | None = None, app_version: str | None = None, *, settings: MgfSettings | None = None, setup_logging: bool = True) -> AppContext` | **stable** (v0.32.0) | The single canonical bootstrap. Wires identity + logging + OTel + excepthooks transactionally; returns the live `AppContext`. `AppContext` is itself a context manager, so both `with bootstrap(...) as ctx:` and `ctx = bootstrap(...)` work. `app_name` and `app_version` are now optional. When omitted, identity auto-derives from `sys.modules["__main__"].__spec__.parent` via the launching package's distribution metadata (works for `python -m <pkg>` invocations + console-script entries that preserve `__spec__`); ad-hoc scripts and test runners fall through to the placeholder identity + an `UnconfiguredWarning`. **`setup_logging=False`** (v0.19.0) skips the StreamHandler + RotatingFileHandler install + `level_overrides`; identity, OTel, excepthooks, AppContext registration still happen. For framework-owned logging stacks (Django LOGGING dictConfig, FastAPI/uvicorn, Flask `app.logger`). Closes BOOTSTRAP-01. |
| `current_context() -> AppContext | None` | **stable** (v0.32.0) | Returns the active `AppContext` (per-task contextvar OR module-global) or `None` if `bootstrap()` has not been called. Thread-safe; ~30 ns per call. |
| `set_identity(app_name: str, app_version: str) -> None` | **experimental** (v0.49.0) | Set the process-wide identity directly, WITHOUT the full `bootstrap()` wiring — for CLIs/tools that want stamped logs but not the observability/lifespan/excepthook stack. Long-running services use `bootstrap()`. Promoted from `_identity._set_identity` (AP-14). |
| `reset_context() -> ContextManager[None]` | **experimental** (v0.49.0) | Context manager that temporarily clears the process-wide bootstrap context (a clean slate for benchmarks/tests), restoring the prior context on exit. Process-wide — affects all threads for the block's duration. Promoted from `_lifecycle._GLOBAL_CONTEXT` (AP-14). |
| `RUNTIME_SNAPSHOT_ENV_VAR: str` | **experimental** (v0.49.0) | Env-var name (`"MGF_DOCTOR_RUNTIME_PATH"`) read by `bootstrap()` — when set, it writes a runtime snapshot there for `mgf-fed app-doctor --live`. Promoted from `_doctor_snapshot` (AP-14). |
| `AppContext` | **stable** (v0.32.0) | Per-tenant identity + settings dataclass. Two construction modes: **bootstrap-owned** (set by `bootstrap()`; carries wiring; shuts down on `with` exit) and **scoped** (`AppContext("name", "ver")` — no wiring; pushes onto the per-task contextvar on `__enter__`, pops on `__exit__`). Scoped contexts implement the multi-tenancy hybrid model from `FEEDBACK.md` §3: hybrid model — process-wide global for the 95% case, contextvar for the 5% library-in-library use cases. Methods: `shutdown()` (LIFO rollback of wiring steps; no-op on scoped), `reload(new_settings)` (atomic re-wire; raises `RuntimeError` on scoped). |
| `app_name() -> str` | **stable** (v0.32.0) | Read the active app name. Resolves via `current_context()` if a context is active, else the legacy module global, else returns `"app"` placeholder + emits `UnconfiguredWarning`. |
| `app_version() -> str` | **stable** (v0.32.0) | Read the active app version. Same resolution order as `app_name`. |
| `UnconfiguredWarning` | **stable** (v0.32.2) | `UserWarning` subclass emitted once per process when identity is read before `bootstrap()`. |
| `__version__: str` | stable | Package version string. Mirrors `pyproject.toml::project.version`. |
| `bootstrap_for_test(app_name="test-app", app_version="0.0.1", *, settings=None) -> AppContext` | **stable** (v0.32.2) | Test-friendly bootstrap. Thin wrapper over `bootstrap()` with `MgfSettings.testing()` defaults — WARNING-level logging, OTel disabled, deterministic preset. Pair with pytest fixtures. See `docs/reference/README.md` §3. |
| `add_help_note(exc, hint, *, doc_ref="")` | **stable** (v0.32.2) | Append a PEP 678 note to ``exc`` carrying an actionable hint (and optional doc / CLI reference). Used internally to make settings-validation errors point at the right env var; consumers MAY use it for their own exception augmentation. |
| `REQUEST_ID_HEADER: str` | **stable** (v0.40.0) | Canonical wire header name (`"X-Request-Id"`) used across federation framework adapters (`mgf-django`, `mgf-fastapi`). Promoted to public per FEEDBACK PAPER-61 so middleware-shape siblings no longer need closed-box-leak carve-outs. |
| `current_request_id() -> str` | **stable** (v0.40.0) | Read the current task's request id. Returns the empty string outside an active request (NOT a raise). Reads a federation-wide ContextVar that `mgf-django` + `mgf-fastapi` middleware set; logging filters + tracing correlate against this. Pre-PAPER-61 name; tier promoted from experimental to stable as part of the PAPER-61 surface graduation. |
| `generate_request_id() -> str` | **stable** (v0.40.0) | Fresh UUID4 string. Separate function so tests can monkeypatch deterministic ids. Renamed from `_generate_request_id` when promoted to public per PAPER-61. |
| `set_request_id(request_id: str) -> contextvars.Token[str]` | **stable** (v0.40.0) | Set the current task's request id; returns the Token to undo with `reset_request_id`. Standard middleware-shape `try / finally` discipline. Wraps the federation's private ContextVar so callers don't touch it directly. New public per PAPER-61. |
| `reset_request_id(token: contextvars.Token[str]) -> None` | **stable** (v0.40.0) | Restore the request id to its prior value. Pair with the Token returned by `set_request_id`. New public per PAPER-61. |

---

## `mgf.common.exceptions`

Typed exception hierarchy. One base + 7 category bases + 7 generic
concretes. Project-specific exceptions subclass these.

### Base

| Name | Tier | Description |
|---|---|---|
| `AppError` | **stable** (v0.32.0) | Base for every exception raised by `mgf.common` and consumer projects. Subclass to extend. |

### Category bases (7)

| Name | Tier | Description |
|---|---|---|
| `ConfigError` | **stable** (v0.32.0) | Configuration / spec validation failures |
| `ProviderError` | **stable** (v0.32.0) | Failures inside a provider implementation (third-party SDK boundary) |
| `OperationError` | **stable** (v0.32.0) | Runtime failures during a long-running operation |
| `GuestError` | **stable** (v0.32.0) | Failures communicating with a remote agent (SSH, agent, health probe) |
| `VaultError` | **stable** (v0.32.0) | Credential vault failures |
| `SchedulerError` | **stable** (v0.32.0) | Scheduler / hook subsystem failures |

### Generic concretes (7)

| Name | Tier | Description |
|---|---|---|
| `SchemaValidationError(errors: list[str])` | **stable** (v0.32.2) | Carries `.errors`; message: `"validation failed: ..."` |
| `AppConfigError` | **stable** (v0.32.2) | Config file missing / malformed / unknown version. Carries `.kind: AppConfigErrorKind \| None` — typed discriminator set by `mgf.common.versioned` load-path raises (closes PAPER-11). Default `None` for legacy / general-purpose raises. |
| `AppConfigErrorKind` | **stable** (v0.32.2) | `StrEnum` of `AppConfigError.kind` values: `FILE_NOT_FOUND` / `IO_READ_FAILED` / `JSON_PARSE_FAILURE` / `YAML_PARSE_FAILURE` / `NON_DICT_ROOT` / `MISSING_VERSION` / `UNSUPPORTED_VERSION` / `MIGRATE_FAILED` / `SCHEMA_VALIDATION`. String values are stable contract — usable as machine-readable kind in serialised crash reports. |
| `CommandError(argv, returncode, stderr)` | **stable** (v0.32.2) | Carries `.argv` / `.returncode` / `.stderr`; subprocess wrapper translation target |
| `ResourceNotFoundError(name: str)` | **stable** (v0.32.2) | Carries `.name` |
| `ResourceAlreadyExistsError(name: str)` | **stable** (v0.32.2) | Carries `.name` |
| `GuestUnreachableError` | **stable** (v0.32.3) | Bare subclass; message in args |
| `GuestCommandError` | **stable** (v0.32.3) | Bare subclass; message in args |
| `BootstrapError` | **stable** (v0.32.2) | Subclass of `OperationError`. Raised by `bootstrap()` when a wiring step fails; carries `.failed_step` + chained cause.2 (transactional bootstrap). |
| `HostEnvironmentError` | **stable** (v0.33.0) | Host pre-flight failures. Renamed from the deprecated `EnvironmentError` (which shadowed Python's built-in alias for `OSError`); the legacy alias was removed in v0.33.0. |
| `CancellationError` | **stable** (v0.32.2) | Subclass of `OperationError`. Raised by `CancellationToken.raise_if_cancelled()` (and the async sibling). Existing `except OperationError:` clauses catch it without code change. Default message: `"operation cancelled by user"`. |

### HTTP / web category (eighth category — closes FEEDBACK HTTP-01, v0.18.0)

Web/API consumers whose natural exception taxonomy is HTTP-status-coded subclass these. Each carries an `http_status: int` class attribute that framework integration (Django middleware, FastAPI exception handler, Starlette handler) reads to map the response status code. Inherits `AppError`, so existing `except AppError:` clauses catch HTTP exceptions too.

| Name | Tier | Description |
|---|---|---|
| `HttpError` | **stable** (v0.32.1) | Base for web/API exceptions. Default `http_status=500`. Three-level category-catch property: `except HttpError:` / `except HttpClientError:` (4xx) / `except HttpServerError:` (5xx). |
| `HttpClientError` | **stable** (v0.32.1) | Base for 4xx — request was bad. `http_status=400`. |
| `HttpServerError` | **stable** (v0.32.1) | Base for 5xx — server failed to fulfill a valid request. `http_status=500`. |
| `HttpBadRequestError` | **stable** (v0.32.1) | 400 — generic client-side validation / parse failure. |
| `HttpUnauthorizedError` | **stable** (v0.32.1) | 401 — authentication required (no / invalid credentials). |
| `HttpForbiddenError` | **stable** (v0.32.1) | 403 — authenticated but not permitted. |
| `HttpNotFoundError` | **stable** (v0.32.1) | 404 — resource doesn't exist. Distinct from `ResourceNotFoundError` (provider-domain). |
| `HttpConflictError` | **stable** (v0.32.1) | 409 — request conflicts with current state. |
| `HttpGoneError` | **stable** (v0.32.1) | 410 — resource existed but is permanently removed. |
| `HttpUnprocessableError` | **stable** (v0.32.1) | 422 — request well-formed but semantically invalid. The canonical "validation failed" status for web APIs. |
| `HttpInternalServerError` | **stable** (v0.32.1) | 500 — generic server-side failure. |
| `HttpServiceUnavailableError` | **stable** (v0.32.1) | 503 — temporarily unavailable. Set `Retry-After` on the response. |
| `HmacVerificationError` | **stable** (v1.0.0) | 401 — webhook HMAC signature failed verification. Raised by `mgf.common.fastapi.webhooks.HmacWebhookVerifier`. Inherits `HttpUnauthorizedError` so PAPER-24's `http_status` resolution maps it without a `status_map` entry. Closes FEEDBACK PAPER-26 (v0.26.0). |

### Process-launch category (closes FEEDBACK process-management batch, v0.21.0)

Raised by `mgf.common.process.run` and `mgf.common.process.Service` when a child process times out, exits non-zero, is signal-killed, or fails to become ready. Each carries `.command` (the argv list) and `.command_id` (the 8-hex-char correlation id stamped onto every captured log line). All four leaves inherit `OperationError` so existing CLI top-level handlers that map `OperationError` → non-zero exit (rule EH-04) catch them unchanged.

| Name | Tier | Description |
|---|---|---|
| `ProcessError` | **stable** (v0.32.3) | Base for `mgf.common.process` failures. Subclass of `OperationError`. |
| `ProcessTimeoutError` | **stable** (v0.32.3) | Child exceeded the `timeout=` deadline. Carries `.timeout: float` (seconds). |
| `ProcessNonZeroExitError` | **stable** (v0.32.3) | Child exited with non-zero return code. Carries `.returncode: int` + `.stderr_tail: str` (last 10 lines of captured stderr for diagnostics). Only raised when `run(check=True)` (the default). |
| `ProcessSignaledError` | **stable** (v0.32.3) | Child was terminated by a signal. POSIX-only failure mode. Carries `.signal: int`. |
| `ServiceNotReadyError` | **stable** (v0.32.3) | A `Service`'s readiness probe never resolved within the configured timeout. Carries `.probe_description: str`, `.timeout: float`, `.last_error: str`. The service is fully torn down before the exception is raised — no leaked process. |

**Total in `mgf.common.exceptions.__all__`: 35 names** (deprecated `EnvironmentError` alias removed in v0.33.0).

---

## `mgf.common.config`

Typed configuration: `BaseAppSettings` + helpers + atomic loader/saver.

| Name | Tier | Description |
|---|---|---|
| `BaseAppSettings` | **stable** (v0.32.1) | pydantic-settings base. subclass-kwarg pattern: `class S(BaseAppSettings, env_prefix="X_"):`. The legacy `model_config = settings_config(env_prefix="X_")` shape still works. |
| `settings_config(**overrides) -> SettingsConfigDict` | **stable** (v0.32.1) | canonical name for building a `SettingsConfigDict` with the recommended baseline. Renamed from `make_settings_config` (removed in v0.33.0). |
| `load_settings(cls, *, path=None, migrate=None) -> T` | **stable** (v1.0.0) | Build a typed `Settings` from disk + env + defaults. Optional migration callback for schema bumps |
| `save_settings(cfg: BaseAppSettings, path: Path) -> None` | **stable** (v1.0.0) | Atomic write (temp + rename, mode 0o600).: `BaseAppSettings.to_yaml_dict()` now recurses into nested sub-models. |
| `platform_dir(kind: PathKind) -> Path` | **stable** (v1.0.0) | Cross-OS XDG / macOS Application Support / Windows AppData resolution |
| `default_config_path(app: str | None = None) -> Path` | **stable** (v1.0.0) | OS-aware default config path; uses `app_name()` if `app` is `None` |
| `expand_path(v: object) -> object` | **stable** (v1.0.0) | Validator helper for `~` and `$VAR` expansion (lazy, per CF-04) |
| `PathKind` | **stable** (v1.0.0) | `Literal["config", "state", "data"]` — the typed alias used by `platform_dir` |

### Pydantic re-exports

| Name | Tier | Description |
|---|---|---|
| `BaseModel` | **stable** (v1.0.0) | Re-exported `pydantic.BaseModel` for nested sub-model definitions inside `Settings` classes. |
| `ConfigDict` | **stable** (v1.0.0) | Re-exported `pydantic.ConfigDict` for inner `model_config = ConfigDict(extra="forbid")` declarations on sub-models. |
| `Field` | **stable** (v1.0.0) | Re-exported `pydantic.Field` for typed-field declarations with constraints (`Field(default=30, ge=1, le=600)`) and aliases. |
| `AliasChoices` | **stable** (v1.0.0) | Re-exported `pydantic.AliasChoices` for renames-without-breaks (rule CF-11). |
| `field_validator` | **stable** (v1.0.0) | Re-exported `pydantic.field_validator` for single-field validators. |
| `model_validator` | **stable** (v1.0.0) | Re-exported `pydantic.model_validator` for cross-field / whole-model validators. |
| `ValidationError` | **stable** (v1.0.0) | Re-exported `pydantic.ValidationError` — caught by consumers when validating user-supplied dicts via `Model.model_validate`. |

**Total in `mgf.common.config.__all__`: 16 names.**

---

## `mgf.common.observability`

Logging + tracing + crash reporting + redaction. OpenTelemetry /
Sentry deps live behind the `[observability]` extra.

| Name | Tier | Description |
|---|---|---|
| `operation_span(name: str, **attrs) -> ContextManager[None]` | **stable** (v0.32.3) | Context manager wrapping a use case in a span. Safe to use even when OTel is disabled |
| `report_now(exc, *, source: str) -> Path` | **stable** (v0.32.3) | Build, persist locally, and best-effort ship a crash report. Returns the local file path. NEVER raises |
| `Redactor` | **stable** (v0.32.3) | Sensitive-field redaction policy used by the formatters. Frozen dataclass; thread-safe. Composes via `\|`. **Perf (PF-06):** `redact()` is **O(n)** in total payload size (keys + nested values + value-string lengths); `redact_string()` is **O(m)** in string length — both linear, pinned by `tests/perf/`'s scaling tests. In-place mutation → O(1) extra allocation per call. |
| `DEFAULT_REDACTION_KEYS` | **stable** (v0.32.3) | The built-in secret-bearing key set, exposed as `frozenset[str]` so consumers can audit / extend without copy-paste. |
| `LogFormat` | **stable** (v0.32.3) | `Literal["text", "json", "auto"]` — the typed alias used by the internal `_wire_logging` plumbing. Consumers see this via the `fmt=` kwarg on `bootstrap()`. |

### path helpers

| Name | Tier | Description |
|---|---|---|
| `default_log_path() -> Path` | **stable** (v0.32.3) | `<state>/<app>/logs/<app>.log` — where `bootstrap()`'s wired logging writes when no explicit log-file path is configured. Lazy (resolves at call time, rule CF-04). Surface in UI for "where do my logs live?". |
| `default_crash_dir() -> Path` | **stable** (v0.32.3) | `<state>/<app>/crashes/` — where `report_now` writes serialised crash reports. Lazy. Surface in UI for crash-report browsing. |
| `default_state_dir() -> Path` | **stable** (v0.32.3) | `<state>/<app>/` — parent of `logs/` and `crashes/`. Honours `XDG_STATE_HOME`; falls back to `~/.local/state`. Directory is NOT created — callers `mkdir` themselves if they need it on disk. |

### closed-box wraps

| Name | Tier | Description |
|---|---|---|
| `get_logger(name=None) -> logging.Logger` | **stable** (v0.32.3) | Returns `logging.getLogger(name)` exposed through us so consumers don't `import logging`. |
| `get_current_span() -> Span | None` | **stable** (v1.0.0) | Returns the active OTel `Span` or `None`. No-op-safe when OTel disabled. |
| `set_attribute(key, value) -> None` | **stable** (v1.0.0) | Set an attribute on the current span. No-op when no span active. |
| `add_event(name, attributes=None, timestamp=None) -> None` | **stable** (v1.0.0) | Add a named event to the current span. No-op when no span active. |
| `set_status(status, description="") -> None` | **stable** (v1.0.0) | Set the current span's status (typically `SpanStatus(StatusCode.ERROR, "...")`). No-op when no span active. |
| `Span` | **stable** (v1.0.0) | Re-exported `opentelemetry.trace.Span` (or `Any` sentinel when OTel not installed). |
| `SpanContext` | **stable** (v1.0.0) | Re-exported `opentelemetry.trace.SpanContext`. |
| `SpanKind` | **stable** (v1.0.0) | Re-exported `opentelemetry.trace.SpanKind`. |
| `SpanStatus` | **stable** (v1.0.0) | Re-exported `opentelemetry.trace.Status` — renamed at our boundary because the bare name `Status` collides with too many other things in consumer code. |
| `StatusCode` | **stable** (v1.0.0) | Re-exported `opentelemetry.trace.StatusCode` — values `OK` / `ERROR` / `UNSET` for span status. |

### registries

| Name | Tier | Description |
|---|---|---|
| `register_crash_sink(name)` | **stable** (v1.0.0) | Decorator: register a custom crash-sink handler. Sink fires when its name appears in `MgfSettings.crash.sinks` (or via legacy `<APP>_CRASH_SINK` env var). |
| `unregister_crash_sink(name)` | **stable** (v1.0.0) | Remove a registered crash sink. No-op if unknown. |
| `CrashSink` | **stable** (v1.0.0) | Type alias: `Callable[[Path, dict[str, Any]], None]` — the crash-sink handler signature. |
| `register_log_handler(name)` | **stable** (v1.0.0) | Decorator: register a custom log-handler factory into the process-global handler registry under `name`. The decorator + registry shape is the stable contract. Note: `mgf-common` does not yet auto-activate registered handlers — settings-driven activation (a future `MgfSettings.logging` key) is planned; until it ships, a consumer reads the registry itself. Registering now is forward-compatible. |
| `unregister_log_handler(name)` | **stable** (v1.0.0) | Remove a registered log handler. |
| `LogHandlerFactory` | **stable** (v1.0.0) | Type alias: `Callable[[MgfSettings], logging.Handler]`. |
| `register_redaction_pattern_group(name)` | **stable** (v1.0.0) | Decorator: register a named tuple of value-patterns. Activated via `MgfSettings.redaction.enabled_pattern_groups`. The "builtin" group is pre-registered. |
| `unregister_redaction_pattern_group(name)` | **stable** (v1.0.0) | Remove a registered pattern group. |
| `PatternGroupFactory` | **stable** (v1.0.0) | Type alias: `Callable[[], tuple[re.Pattern[str], ...]]`. |
| `register_span_processor(name)` | **stable** (v1.0.0) | Decorator: register a custom OTel span-processor factory into the process-global processor registry under `name`. The decorator + registry shape is the stable contract. Note: `mgf-common` does not yet auto-activate registered processors — settings-driven activation (a future `MgfSettings.otel` key) is planned; until it ships, a consumer reads the registry itself. Registering now is forward-compatible. |
| `unregister_span_processor(name)` | **stable** (v1.0.0) | Remove a registered span processor. |
| `SpanProcessorFactory` | **stable** (v1.0.0) | Type alias: `Callable[[MgfSettings], SpanProcessor]`. |
| `RedactionGroup` | **stable** (v1.0.0) | `str`-Enum of canonical pattern-group names for `RedactionSettings.enabled_pattern_groups`. One member: `BUILTIN = "builtin"` (the always-on Bearer/JWT/PEM/API-key set). Inherits from `str`, so passing the enum to settings is identical to passing the string but type-checked. Mixed enum + str (consumer-registered groups) works because each member is a `str`. |

### — async logging path

| Name | Tier | Description |
|---|---|---|
| `JsonFormatter` | **stable** (v0.32.3) | One-JSON-object-per-line formatter. Promoted to the public surface in (was previously importable but not in `__all__`). OTel-aware (`trace_id`/`span_id` injected when a span is active). Redactor-aware. Constructor: `JsonFormatter(*, redactor: Redactor)`. |
| `AsyncJsonHandler` | **stable** (v1.0.0) | Non-blocking logging handler. Wraps a target handler; `emit()` puts records on a bounded `queue.Queue` and returns immediately; a daemon thread drains the queue into the target. Configurable overflow policy (`drop` with one-shot warning + `dropped_count` counter, OR `block` for backpressure). `close()` drains pending records then joins the worker. Constructor: `AsyncJsonHandler(*, target: logging.Handler, max_queue_size: int = 10_000, on_overflow: Literal["drop", "block"] = "drop", drain_interval: float = 0.5)`. **Perf (PF-06):** `emit()` is **O(1)** on the calling thread — a bounded-queue put + record hand-off; the formatter (`O(n)`) runs on the drain thread, which is the whole point. Pinned by `tests/perf/`'s async-vs-sync ratio. |
| `LogRecord` | **experimental** (unified-logging Wave 1) | Canonical cross-language structured log record — the logging analog of `TestRecord`. Formalizes `JsonFormatter`'s wire shape (`ts`/`level`/`logger`/`msg`/…) + adds `app`/`app_version`/`host`/`lang`/`level_no`/`env`/`labels` and a `fields` context map. Methods: `from_python(logging.LogRecord, *, app, app_version, env)`, `to_dict()`/`to_jsonl_line()`, `from_dict()`/`from_jsonl_line()`. The schema every Magogi runtime emits + every consumer (CLI tail, web stream, shipper) reads. |
| `RateLimitFilter` | **stable** (LG-13) | `logging.Filter` capping records/sec per (logger, level); excess dropped, with a once-per-window WARNING reporting the drop count. `handler.addFilter(RateLimitFilter(max_per_second=200))`. Thread-safe. |
| `SampleFilter` | **stable** (LG-13) | `logging.Filter` keeping 1-in-`sample_rate` records (telemetry where the distribution matters, not individuals). |
| `DedupFilter` | **stable** (LG-13) | `logging.Filter` collapsing identical (logger, level, msg) within `window_s`; bounded — expired keys are pruned. Thread-safe. |
| `LOG_SCHEMA_VERSION` | **experimental** | `str` — the `LogRecord` schema version (`"1.0.0"`); evolve additively. |
| `describe_logging() -> LogInfo` | **experimental** (unified-logging Wave 1) | Introspection: the effective logging picture for the running process — root level, active sinks (+ targets), per-component logger levels in effect, and canonical sink locations. Reads the live `logging` tree, so it reflects reality. Surfaced by `mgf-common logs info` + the web `/logs/info` endpoint. |
| `LogInfo` | **experimental** | Frozen dataclass returned by `describe_logging()`: `app`/`app_version`/`root_level`/`root_level_no`/`handlers`/`component_levels`/`locations`. Has `to_dict()`. |
| `HandlerInfo` | **experimental** | Frozen dataclass — one active sink: `kind`/`level`/`formatter`/`target`. Has `to_dict()`. |
| `log_schema_manifest() -> dict` | **experimental** (unified-logging Wave 1.5) | The language-neutral `LogRecord` wire contract a Zig/TS porter builds against without reading Python: `schema_version`, the always-present `core` keys, the full `emit_order`, each field's type + omit rule, and the serialisation `rules`. Pinned to the live schema + the golden fixture by `test_log_record_conformance` (OB-10). |
| `LogStatsCollector` | **experimental** (unified-logging Wave 4) | A `logging.Handler` that counts records for per-app statistics — cheap (O(1), no I/O in `emit`), bounded (per-component map capped, overflow → `__other__`), fail-safe (`emit` never raises). Ctor: `LogStatsCollector(*, window_seconds=60.0, max_components=200, level=NOTSET)`. `snapshot() -> LogStats`; `reset()`. (OB-11) |
| `LogStats` | **experimental** (unified-logging Wave 4) | Frozen snapshot dataclass: `total`/`by_level`/`by_component`/`warnings`/`errors`/`dropped` (async-overflow) + `started_at`/`uptime_seconds`/`rate_per_second`/`window_seconds`/`window_rate_per_second`/`component_cardinality_capped`/`schema_version`. JSON-able via `to_dict()` for the web surface + central ship. |
| `enable_log_stats(*, logger=None, level=NOTSET, window_seconds=60.0, max_components=200, replace=False) -> LogStatsCollector` | **experimental** (unified-logging Wave 4) | Install a process-wide `LogStatsCollector` (idempotent; `replace=True` swaps a fresh one). Attaches to root unless `logger` given. |
| `current_log_stats() -> LogStats \| None` | **experimental** (unified-logging Wave 4) | Snapshot the process-wide collector, or `None` if stats aren't enabled. |
| `disable_log_stats() -> None` | **experimental** (unified-logging Wave 4) | Detach + clear the process-wide collector (idempotent). |

**Total in `mgf.common.observability.__all__`: 48 names**.

---

## `mgf.common.settings`

The library's own typed settings root (closes FEEDBACK design
ref §4.2 — single source of truth for every behavioural knob).
of the redesign.

| Name | Tier | Description |
|---|---|---|
| `MgfSettings` | **stable** (v0.32.1) | Single typed root — composes `LoggingSettings` / `OtelSettings` / `CrashSettings` / `RedactionSettings` / `VaultSettings` + `preset`. `BaseAppSettings` subclass with `env_prefix="MGF_"`. three intent-driven factory classmethods — `MgfSettings.dev()` (DEBUG + text logging, OTel off), `MgfSettings.production()` (deterministic explicit preset + JSON logging + 0.05 OTel sample rate), `MgfSettings.testing()` (WARNING-level logging, OTel off, deterministic). All accept the same `**kwargs` shape as the constructor; user-supplied kwargs always win over factory defaults. `preset` accepts a list applied left-to-right (`preset=["docker", "auto"]`); `[tool.mgf]` in pyproject.toml is now a settings source between env vars and defaults (kill switch: `MGF_DISABLE_PYPROJECT=1`). |
| `LoggingSettings` | **stable** (v0.32.1) | Logging fields: level / format / file / journald / syslog_address / http_sink / rotating_max_bytes / rotating_backup_count / level_overrides.6 (per-component levels first-class). |
| `OtelSettings` | **stable** (v0.32.1) | OTel fields: enabled / endpoint / service_namespace / sample_rate / resource_attributes / httpx_instrumented / subprocess_instrumented / metrics_enabled. |
| `CrashSettings` | **stable** (v0.32.1) | Crash-reporter fields: sinks / sentry_dsn / otlp_endpoint / http_url / state_dir. will introduce the registry. |
| `RedactionSettings` | **stable** (v0.32.1) | Redactor fields: extra_keys / extra_value_patterns / enabled_pattern_groups / strict_mode.12 (GDPR strict mode — wires the patterns). |
| `LogLevel` | **stable** (v1.0.0) | `Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]` — stable type alias. |
| `LogFormat` | **stable** (v1.0.0) | `Literal["text", "json", "auto"]` — moved here from `mgf.common.observability` (legacy alias still works). |
| `PresetName` | **stable** (v1.0.0) | `Literal["explicit", "dev", "systemd", "docker", "auto"]` — preset selector. ships the seam; wires the preset semantics. |

| `VaultSettings` | **stable** (v0.32.3) | Vault backend selection: `backend` (str, default `"env"`) + `config` (dict). keeps `config` an untyped dict; ships a discriminated union. |

**Total in `mgf.common.settings.__all__`: 9 names.**

---

## `mgf.common.typing` Single-source `Protocol` definitions for
every stable abstraction so consumers can pass duck-typed
objects (test doubles, custom impls) where the library accepts
the protocol. of the redesign.

| Name | Tier | Description |
|---|---|---|
| `CancellationProtocol` | **stable** (v0.32.3) | Cooperative-cancellation contract: `cancel() -> None` + `is_cancelled() -> bool`. Concrete implementations: `CancellationToken` (sync) + `AsyncCancellationToken` (async). `runtime_checkable` so `isinstance(obj, CancellationProtocol)` works. |
| `ReporterProtocol` | **stable** (v0.32.3) | Progress-reporter contract: `report(message, current=0, total=0) -> None` + `is_cancelled() -> bool`. Re-exported as `mgf.common.progress.ProgressReporter` for ergonomics. `runtime_checkable`. |
| `LogSinkProtocol` | **stable** (v0.32.3) | Append-style log sink contract: `append_info(message)` + `append_error(message)`. For the future GUI helpers (Qt / Tk / Flet) — consumers in non-Qt frameworks implement this against their widget. `runtime_checkable`. |
| `VaultProtocol` | **stable** (v0.32.3) | Credential-vault contract: `get / set / delete / list_keys` + `backend` property. Concrete implementations land in `mgf.common.vault` (). `runtime_checkable`. |

**Total in `mgf.common.typing.__all__`: 4 names.**

---

## `mgf.common.progress` Cooperative cancellation + progress-
reporting primitives lifted from vm-vmanager-app. of the
redesign.

| Name | Tier | Description |
|---|---|---|
| `CancellationToken` | **stable** (v0.32.3) | Thread-safe sync cancellation token. `cancel() / is_cancelled() / wait(timeout) / raise_if_cancelled()`. Backed by `threading.Event`. Implements `CancellationProtocol`. |
| `ProgressReporter` | **stable** (v0.32.3) | Ergonomic alias for `mgf.common.typing.ReporterProtocol`. |
| `NullReporter` | **stable** (v0.32.3) | No-op concrete impl of `ProgressReporter`. Optionally takes a `token=` parameter to forward `is_cancelled()` to a real cancellation token. |
| `RecordingReporter` | **stable** (v0.32.3) | Test-double impl of `ProgressReporter`. Accumulates calls in `.calls: list[tuple[str, int, int]]` for assertions. `reset()` clears between sub-tests. |

**Total in `mgf.common.progress.__all__`: 4 names.**

### `mgf.common.progress.asyncio`

Async sibling submodule per the sync-first / async-adapter rule
(CLOSED_BOX_REDESIGN.md §14.1). Thin shim — never a duplicate
of the sync API.

| Name | Tier | Description |
|---|---|---|
| `AsyncCancellationToken` | **stable** (v0.32.3) | asyncio-native cancellation token. `cancel() / is_cancelled()` (sync) + `await wait(timeout)` (async) + `raise_if_cancelled()` (sync helper). Backed by `asyncio.Event`. Implements `CancellationProtocol`. Bound to a single event loop — NOT thread-safe across loops. |

**Total in `mgf.common.progress.asyncio.__all__`: 1 name.**

---

## `mgf.common.vault` Credential-storage backends. Three built-in
backends + a registry for custom ones (HashiCorp Vault, AWS
Secrets Manager). of the redesign.

| Name | Tier | Description |
|---|---|---|
| `EnvVault` | **stable** (v0.32.3) | Stdlib-only env-var backend. Maps each key to `<prefix><KEY>` (uppercased; default prefix `"MGF_VAULT_"`). The right backend for CI / containerised deployments. Audit-logs every op per rule SC-17. |
| `KeyringVault` | **stable** (v0.32.3) | System credential store via the `keyring` library (libsecret / Keychain / Credential Manager). Requires `mgf-common[vault]`. `service` parameter for the keyring service name (default `"mgf-common"`); consumer apps SHOULD override per rule SC-12. Tracks key index in a per-service `__mgf_index__` entry for portable `list_keys()`. |
| `EncryptedFileVault` | **stable** (v0.32.3) | Fernet (AES-128 + HMAC-SHA256) encrypted JSON file. Key derivation: PBKDF2-HMAC-SHA256, 600 000 iterations (OWASP 2023 baseline per SC-05). 16-byte per-vault salt persisted in the file header. Atomic write per CF-04, `0o600` at-rest permissions. Requires `mgf-common[vault]`. |
| `register_vault_backend(name)` | **stable** (v1.0.0) | Decorator: register a custom vault-backend factory. Built-in names (`"env"`, `"keyring"`, `"file"`) are pre-registered at import time. Closes the closed-box leak around third-party stores. |
| `unregister_vault_backend(name)` | **stable** (v1.0.0) | Remove a registered vault backend. No-op if unknown. |
| `VaultBackendFactory` | **stable** (v1.0.0) | Type alias: `Callable[[dict[str, Any]], VaultProtocol]`. The shape every registered factory implements (the dict shape stays for consumer-registered backends; built-ins also accept typed configs as of). |

### Typed configs (closes the closed-box leak)

The ``VaultSettings.config: dict[str, Any]`` was stringly-typed.
lands typed configs that close the leak. Built-in factories
accept the typed shape (recommended) OR the dict shape (legacy —
emits `MgfDeprecationWarning`). Custom backends keep the dict
shape until the registry grows config-model registration in.

| Name | Tier | Description |
|---|---|---|
| `EnvVaultConfig(prefix: str = "MGF_VAULT_")` | **stable** (v0.32.3) | Typed config for the built-in `"env"` backend. Frozen `BaseModel` with `extra="forbid"` + `backend_type: Literal["env"]` discriminator. Pydantic enforces the shape at construction (typo'd field fails loudly; mutation after construction raises). |
| `KeyringVaultConfig(service: str = "mgf-common")` | **stable** (v0.32.3) | Typed config for the built-in `"keyring"` backend. Same shape rules as `EnvVaultConfig`. Consumer apps SHOULD override `service` per rule SC-12. |
| `EncryptedFileVaultConfig(path: Path, passphrase: str)` | **stable** (v0.32.3) | Typed config for the built-in `"file"` backend. Both fields are required (pydantic enforces — vs the runtime check in the factory). |
| `BuiltinVaultBackendConfig` | **stable** (v1.0.0) | `Annotated[Union[EnvVaultConfig, KeyringVaultConfig, EncryptedFileVaultConfig], Field(discriminator="backend_type")]` — tagged discriminator union over the three built-in typed configs. Used as the base of `VaultSettings.config` typing for built-ins (the field also accepts `dict[str, Any]` for consumer-registered backends). |

**Total in `mgf.common.vault.__all__`: 10 names**.

---

## `mgf.common.versioned`

Versioned-file store primitive. Captures the load-validate-migrate-atomic-save
shape every satellite config file (hooks, schedules, alert specs, profiles)
ships by hand. Distinct from `mgf.common.config.BaseAppSettings` — that's
the **single** top-level settings root with multi-source layering;
`VersionedFileStore[T]` is many-per-app, document-per-instance, no layering.

| Name | Tier | Description |
|---|---|---|
| `VersionedFileStore[T](path, *, schema, current_version, migrate=None, version_field="version", file_format=None, tolerate_missing_version=False)` | **stable** (v0.32.3) | Generic versioned-file store. `schema` MUST set `model_config = ConfigDict(extra="forbid")`. Format auto-detects from path suffix (`.yaml`/`.yml` or `.json`). `load()` returns `T`; `save(value)` stamps `version_field=current_version` and writes atomically (temp + rename, fsync, mode 0o600). `exists()` / `path` for callers that need to guard. `tolerate_missing_version=True` opts in to a grace mode for pre-versioning legacy files: missing field logs one WARNING via the `mgf.common.versioned` logger and is treated as `current_version` (skipping migrate); files where the field is *present but not an int* still raise. Errors land as `AppConfigError` with PEP 678 notes naming the path + offending key. |
| `MigrateFn` | **stable** (v0.32.3) | `Callable[[Mapping[str, Any], int], Mapping[str, Any]]` — receives the raw parsed payload and the on-disk version, returns the migrated raw payload. Multi-step migrations are the callback's responsibility (the store calls it once per load). |

**Total in `mgf.common.versioned.__all__`: 2 names**.

---

## `mgf.common.registry`

Generic registry primitive . The shared shape across the
+ hand-rolled registries (crash sinks, log handlers, redaction
patterns, span processors, vault backends, CLI subcommands), factored
out into one parametric type. The existing six registries are NOT
migrated to this primitive — they work and migration carries risk;
the primitive is the shape for FUTURE registries (e.g., the
 `register_vault_backend(config_model=...)`
extension; consumer-built registries).

| Name | Tier | Description |
|---|---|---|
| `Registry[F]` | experimental | Generic name → factory map. Public methods: `register(key)` (decorator), `unregister(key)` (no-op), `get(key)`, `snapshot()` (shallow copy), `__iter__`, `__contains__`, `__len__`. Threading: register at import / bootstrap time, not from a worker. |
| `make_registry(name) -> Registry[F]` | experimental | Construct a `Registry` with the given name. The recommended construction path; `name` is used in error messages + by future diagnostic-CLI introspection. Generic parameter `F` is inferred at the call site if you annotate the assignment. |

**Total in `mgf.common.registry.__all__`: 2 names.**

---

## `mgf.common.cli`

Top-level CLI helpers — stable exit-code constants + an exception
→ exit-code translator. of the redesign.

### Exit-code constants

| Name | Tier | Description |
|---|---|---|
| `EXIT_SUCCESS` | **stable** (v0.32.3) | `0` — clean exit. |
| `EXIT_CONFIG_ERROR` | **stable** (v0.32.3) | `1` — `ConfigError` family. |
| `EXIT_OPERATION_ERROR` | **stable** (v0.32.3) | `2` — `OperationError` family (includes `CancellationError` and `BootstrapError` by default). |
| `EXIT_GUEST_ERROR` | **stable** (v0.32.3) | `3` — `GuestError` family. |
| `EXIT_ENVIRONMENT_ERROR` | **stable** (v0.32.3) | `4` — `HostEnvironmentError` family. |
| `EXIT_PROVIDER_ERROR` | **stable** (v0.32.3) | `5` — `ProviderError` family (includes `ResourceNotFoundError` / `ResourceAlreadyExistsError`). |
| `EXIT_VAULT_ERROR` | **stable** (v0.32.3) | `6` — `VaultError` family. |
| `EXIT_APP_ERROR` | **stable** (unreleased — next bump) | `7` — base-level fallback for any other `AppError` subclass (e.g. `SchedulerError`, the `HttpError` family). Guarantees every TYPED error gets a typed exit and is never mistaken for a crash; no crash report shipped. |
| `EXIT_UNKNOWN` | **stable** (v0.32.3) | `10` — genuinely *untyped* (non-`AppError`) exception escaped to the top level. The catch-all branch ALSO ships a crash report via `report_now`. |

### Translator

| Name | Tier | Description |
|---|---|---|
| `run_and_translate(main_fn, argv, *, exit_code_map=None, crash_source="cli") -> int` | **stable** (v0.32.3) | Run `main_fn(argv)`; translate exceptions to exit codes via MRO walk. Untyped exceptions → `EXIT_UNKNOWN` + `report_now`. NEVER raises. |
| `translate_exceptions(*, exit_code_map=None, crash_source="cli")` | **stable** (v0.32.3) | Decorator form of `run_and_translate`. The wrapped callable returns `int` (always the exit code). |
| `main_entry(main_fn, *, exit_code_map=None, crash_source="cli")` | **stable** (v0.32.3) | Convenience: read `sys.argv`, translate, and `sys.exit`. The shortest possible top-level. |

### Settings provenance (promoted from private in)

| Name | Tier | Description |
|---|---|---|
| `field_provenance(settings, *, init_overrides=None, file_overrides=None) -> dict[str, dict[str, FieldSource]]` | **stable** (v0.32.3) | Per-field source attribution for an `MgfSettings` instance: walks each field through env → file → preset → init → default and reports the first hit. The same shim that powers `mgf-common explain`, exposed publicly so consumers can build their own settings-debug UIs without reaching past us into pydantic-settings. |
| `env_vars_known_to_mgf() -> list[tuple[str, str | None]]` | **stable** (v0.32.3) | Sorted list of every env var `MgfSettings` would consult, paired with its current value (or `None` if unset). Useful for support tooling — print-friendly, deterministic, no side effects. |
| `FieldSource` | **stable** (v0.32.3) | Frozen dataclass `(kind: SourceKind, detail: str)` returned by `field_provenance`. `detail` is human-readable extra context (e.g. `"MGF_LOGGING__LEVEL=DEBUG"` for env-supplied fields, `"systemd"` for preset-supplied fields). |
| `SourceKind` | **stable** (v0.32.3) | `Literal["default", "env", "preset", "init", "file"]` — the typed alias used by `FieldSource.kind`. |

### Subcommand registry (composable CLI registry)

Closes the closed-box leak around CLI extension: consumers building CLIs reached past `mgf.common.cli` to `argparse`
directly, re-implementing exit-code translation on every consumer.
The registry is the single source of truth for "what subcommands
does the CLI know about?"; both the diagnostic `mgf-common` CLI
AND consumer-built top-level CLIs (e.g. `vmanager`) read from it.

The four built-in subcommands (`explain` / `validate` / `doctor` /
`migrate`) are pre-registered against the same primitive consumers
use — eat-your-own-dogfood.

| Name | Tier | Description |
|---|---|---|
| `register_cli_subcommand(name, *, handler, help, parser_setup=None)` | experimental | Register a subcommand. Two call shapes: plain (pass `handler=`) or decorator (omit `handler=`; the decorated function is registered as the handler). Raises `ValueError` if `name` is already registered. |
| `subcommand` | experimental | Short alias for `register_cli_subcommand` — same function object (`subcommand is register_cli_subcommand`). Matches click's `@click.command` idiom for ergonomic call sites. The long form stays canonical; the alias is for terse decorators. |
| `unregister_cli_subcommand(name)` | experimental | Remove a registered subcommand. No-op if `name` is unknown. |
| `CliSubcommand` | experimental | Frozen dataclass record returned by the internal registry snapshot. Public so consumers + tests can introspect (`name`, `help`, `handler`, `parser_setup`). |
| `build_cli(prog="mgf-common", *, description=None, include_builtins=True) -> ArgumentParser` | experimental | Construct an `argparse.ArgumentParser` populated from the registry. Pass `prog="myapp"` to ship a consumer top-level CLI; pass `include_builtins=False` to expose ONLY consumer-registered subcommands. Pair with `dispatch(args, parser=parser)` for the bottom half. |
| `dispatch(args, *, parser=None) -> int` | experimental | Look up `args.command` in the registry and run its handler. Returns the handler's exit code; falls back to `EXIT_UNKNOWN` (with help printed to stderr if `parser` supplied) when the command is empty or unknown. The bottom half of consumer top-level CLIs built on `build_cli`. Promoted from private after the `tests/public_surface/` audit surfaced it as a missing pair to `build_cli`. |

**Total in `mgf.common.cli.__all__`: 21 names**.

### Console-script entry point (b + )

| Name | Tier | Description |
|---|---|---|
| `mgf-common` (CLI command) | experimental | Console-script registered in `pyproject.toml`. Subcommands: `explain` (renders active settings with per-field provenance — default / env / preset / init / file), `validate <config.yaml>` (schema-validate a YAML file against `MgfSettings`), `doctor` (install-state + wiring health checks; `--standards` runs a code-level conformance audit against the current project for AP-09 / AP-13 / TS-04 / DP-12), `migrate <from> <to> <path>` (regex-based codemod for mechanical renames; per-rule import gates so it never over-applies). Exit codes per `mgf.common.cli` constants. |

The Python implementation modules (`mgf.common.cli._mgfcli` /
`_explain` / `_validate` / `_doctor` / `_provenance`) are private
(underscore-prefixed); consumers depend on the **CLI command**,
not the import paths. Programmatic use of the diagnostic
functions can be promoted to the public API on consumer demand
per rule SP-04 in `docs/standards/SCOPE.md`.

---

## `mgf.common.pytest_plugin`

Auto-registered pytest fixtures + helpers for testing apps that use
`mgf-common`. Discovered via the `pytest11` entry point in
`pyproject.toml` — no `pytest_plugins` configuration needed in the
consumer's conftest.

| Name | Tier | Description |
|---|---|---|
| `mgf_settings` (fixture) | **stable** (v1.0.0) | Function-scoped fresh `MgfSettings.testing()` instance. |
| `mgf_context` (fixture) | **stable** (v1.0.0) | Function-scoped bootstrapped `AppContext` (auto-shutdown LIFO). |
| `mgf_context_with_capture` (fixture) | **stable** (v1.0.0) | Like `mgf_context` plus `capture_logs=True, capture_spans=True`. Span capture requires `[observability]` extra + `mgf_otel_provider` (transitive dep). |
| `mgf_otel_provider` (fixture, session scope) | **stable** (v1.0.0) | Installs an SDK `TracerProvider` once per session. Skips if `[observability]` extra is missing. Defers to an already-installed SDK provider. |
| `redaction_recorder` (fixture) | **stable** (v1.0.0) | Records every `Redactor.redact` call's pre/post payload for assertion. Function-scoped (resets per test). |
| `decode_json_logs(caplog) -> list[dict]` | **stable** (v1.0.0) | Helper that JSON-decodes records from pytest's `caplog`. Records that aren't JSON are silently skipped. NOT a fixture — call directly. |

Disable per-run: `pytest -p no:mgf_common`. See
[`docs/recipes/testing.md`](docs/recipes/testing.md) for
the walkthrough.

---

## `mgf.common.testing`

Portable types + helpers for the federation test supervisor
(TS-22..TS-27; see `docs/standards/TESTING.md` §19). The supervisor
itself is at `scripts/_mtest/` + `mgf-common test` CLI; this
subpackage exports the **library-shaped** parts that downstream
tooling (consumers reading `run.jsonl`, dashboards, status badges)
also needs.

| Name | Tier | Description |
|---|---|---|
| `TestRecord` | **stable** (v0.40.0) | Frozen dataclass: one per-test outcome (TS-24). One line in `.gates/last-run/run.jsonl`. `to_jsonl_line()` / `from_jsonl_line()` are the canonical writers/readers. Schema mirrors `DoctorFinding` (code/severity/summary/detail/why/remediation) plus test-specific fields (test_id, outcome, duration_ms, tier, markers, captured_stdout, captured_stderr, run_id, ts_iso, schema_version). The nullable `perf` field (TS-28) carries per-test perf attribution when captured. |
| `Outcome` | **stable** (v0.40.0) | `str`-mixin enum: `PASSED`/`FAILED`/`ERROR`/`SKIPPED`/`HUNG`/`CRASHED`/`QUARANTINED`. The last three are the supervisor's additions over pytest's native four. |
| `Severity` | **stable** (v0.40.0) | `str`-mixin enum: `OK`/`WARN`/`FAIL`/`SKIP`. Matches `DoctorFinding.Severity` so a single triage UI can render both. |
| `DEFAULT_SEVERITY: dict[Outcome, Severity]` | **stable** (v0.40.0) | Canonical mapping from outcome to the severity the supervisor stamps by default. `PASSED→OK`, `QUARANTINED→WARN`, `FAILED/ERROR/HUNG/CRASHED→FAIL`, `SKIPPED→SKIP`. |
| `SCHEMA_VERSION: str` | **stable** (v0.40.0) | Current JSONL schema version (`"1.1.0"` — `1.1.0` added the nullable `perf` field for TS-28; `1.0.0` was the original TS-24 shape). AP-04 deprecation cycle applies if a field is renamed / removed. |
| `MARKER_TIER: dict[str, int]` | **stable** (v0.40.0) | Federation marker → tier mapping (TS-22; see TESTING.md §13.11). Consumers MAY override per-project but SHOULD only ADD entries (preserves cross-project tier semantics). |
| `DEFAULT_TIER: int` | **stable** (v0.40.0) | Tier for unmarked tests (`1` — unit). |
| `effective_tier(markers, override=None, mapping=None) -> int` | **stable** (v0.40.0) | Compute a test's tier from markers. Resolution: explicit `override` wins → max marker tier → `DEFAULT_TIER`. Pass `mapping` for a project-local marker set; unknown markers ignored. |
| `TierBudget` | **stable** (v0.40.0) | Frozen dataclass: `name` / `per_test_seconds` / `suite_seconds` / `description`. The four budgets are pinned in `TIERS`. |
| `TIERS: tuple[TierBudget, ...]` | **stable** (v0.40.0) | The four canonical tiers in order: `tier-0` (0.10s/test, 1s suite, "Build-broken signal"), `tier-1` (0.30s/30s, "Daily contract"), `tier-2` (2s/120s, "Promise pins"), `tier-3` (10s/1800s, "Long signal — stress/perf/e2e"). |
| `default_sock_path(prefix, worker=None, *, tmpdir=None) -> Path` | **stable** (v0.40.0) | Parallel-safe Unix socket path for a test daemon. Reads `PYTEST_XDIST_WORKER` env var (`"main"` fallback) so workers don't fight over the same `/tmp/<prefix>.sock`. Rejects filesystem-unsafe prefixes/workers (path separators, NUL, whitespace, shell metachars). |
| `FailureBundle` | **stable** (v0.40.0) | Frozen dataclass: per-failure detail bundle (TS-27). Ten canonical sections (TEST / OUTCOME / TRACEBACK / STDOUT / STDERR / PROCESS STATE / DMESG TAIL / ENV / HISTORY / NEXT STEPS); custom sections appended after. `serialize()` produces the plain-text wire form with `=== <section> ===` markers. |
| `SECTION_ORDER: tuple[str, ...]` | **stable** (v0.40.0) | Frozen tuple of the ten canonical section names. Re-order = breaking change. |
| `SECTION_HEADER_FMT: str` | **stable** (v0.40.0) | Format string `"=== {name} ==="` — the grep-friendly section-marker format. |
| `perf_span(label)` | **experimental** (TS-28) | Context manager that times a named code/log section (nestable, fail-open, near-zero cost when capture is inactive). Wrap a DB call, a render, the block around a log line — the supervisor surfaces each section's wall-time. Capture is activated by `mtest --perf` (env `MGF_PERF`); outside that it's a bare `yield`. |
| `PerfSummary` | **experimental** (TS-28) | Frozen dataclass: the per-test perf artifact written by the pytest plugin and read by the supervisor — `test_id` / `total_ms` / `functions` / `spans` / `schema_version`. `to_dict()` / `to_json()` produce the wire shape stored in `TestRecord.perf`. |
| `PerfFunction` | **experimental** (TS-28) | Frozen dataclass: one `cProfile` frame — `func` (`path:lineno(name)`) / `ncalls` / `tottime_ms` (self) / `cumtime_ms` (incl. subcalls). |
| `PerfSpan` | **experimental** (TS-28) | Frozen dataclass: one `perf_span` section sample — `label` / `ms` / `depth` (nesting level). |
| `PERF_SCHEMA_VERSION: str` | **experimental** (TS-28) | Schema version of the perf artifact wire shape (`"1.0.0"`). |
| `DEFAULT_TOP_N: int` | **experimental** (TS-28) | Default cap on `cProfile` function rows captured per test (`15`). |

20 names. The first 14 are `stable (v0.40.0)`; the six `perf_*` /
`Perf*` names are `experimental (TS-28)`, added for per-test perf
attribution. The supervisor (`mgf-test-supervisor`) imports
`TestRecord` / `MARKER_TIER` / etc. from this module rather than
duplicating; downstream tooling does the same.

---

## Grand totals

The five subpackages extracted to sibling libraries (`mgf.common.fastapi`, `mgf.common.http`, `mgf.common.db`, `mgf.common.alembic`, `mgf.common.django`) are no longer counted here — see the stub redirects below for the new homes.

| Package | Public names |
|---|---|
| `mgf.common` | 10 (9 in `__all__` + `__version__`) |
| `mgf.common.exceptions` | 36 |
| `mgf.common.config` | 16 |
| `mgf.common.observability` | 51 |
| `mgf.common.settings` | 9 |
| `mgf.common.typing` | 4 |
| `mgf.common.progress` | 4 |
| `mgf.common.progress.asyncio` | 1 |
| `mgf.common.vault` | 10 |
| `mgf.common.versioned` | 2 |
| `mgf.common.registry` | 2 |
| `mgf.common.cli` | 21 |
| `mgf.common.pytest_plugin` | 6 |
| `mgf.common.testing` | 20 |
| `mgf.common.providers` | 2 |
| `mgf.common.process` | 10 |
| `mgf.common.flags` | 6 |
| `mgf.common.schedule` | 7 |
| `mgf.common.plugins` | 3 |
| `mgf.common.doctor` | 6 |
| `mgf.common.rate_limit` | 2 |
| `mgf.common.idempotency` | 4 |
| `mgf.common.health` | 7 |
| `mgf.common.cache` | 4 |
| **Total** | **228** (manual count post-federation-split; re-run the PUBLIC_API.json auto-introspector to authoritatively verify against live `__all__` counts) |

---

## Notes on the experimental tier

Every public name is `experimental`. Promotion to `stable` happens release-by-release as
the early-consumer feedback in [`FEEDBACK.md`](FEEDBACK.md)
converges. The promotion path:

1. A name survives one full MINOR cycle without a 🔴 Blocker
   filed against it in [`FEEDBACK.md`](FEEDBACK.md).
2. The maintainer reviews the name's actual usage in the reference
   consumer (vm-vmanager-app) and confirms the shape is right.
3. The name moves from `experimental` to `stable` in the next
   release; this `PUBLIC_API.md`'s tier column updates; no code
   change required.

A name marked `stable` in a v1.x release follows the full deprecation
cycle (rule **AP-04**) — its removal happens only in a future MAJOR
release, with one MINOR cycle of `DeprecationWarning` survival.

## `mgf.common.db`

**Moved to sibling library [`mgf-sqlalchemy`](https://codeberg.org/magogi-admin/mgf-sqlalchemy).**

Extracted from mgf-common at v0.30.0 per ASPIRE-06 (federation split). The pure-SQLAlchemy adapters now live in `mgf-sqlalchemy`; the FastAPI-shaped helpers (`get_session` / `setup_db`) moved to `mgf.fastapi.db` (gated behind `mgf-fastapi[sqlalchemy]`) so the SQLAlchemy sibling stays framework-agnostic. Consumers install the sibling directly:

```bash
uv add mgf-sqlalchemy              # was: pip install 'mgf-common[db]'
uv add 'mgf-sqlalchemy[test]'      # was: 'mgf-common[db-test]'
```

See [mgf-sqlalchemy's PUBLIC_API.md](https://codeberg.org/magogi-admin/mgf-sqlalchemy/src/branch/main/PUBLIC_API.md) for the current public surface. Migration history is in `docs/cutover/v0.30.0.md`.

---

## `mgf.common.alembic`

**Moved to sibling library [`mgf-alembic`](https://codeberg.org/magogi-admin/mgf-alembic).**

Extracted from mgf-common at v0.30.0 per ASPIRE-06 (federation split), alongside `mgf-sqlalchemy`. Consumers wanting the alembic `env.py` helper install the sibling directly:

```bash
uv add mgf-alembic                 # was: pip install 'mgf-common[alembic]'
uv add 'mgf-alembic[test]'         # was: 'mgf-common[alembic-test]'
```

See [mgf-alembic's PUBLIC_API.md](https://codeberg.org/magogi-admin/mgf-alembic/src/branch/main/PUBLIC_API.md) for the current public surface. Migration history is in `docs/cutover/v0.30.0.md`.

---

## `mgf.common.django`

**Moved to sibling library [`mgf-django`](https://codeberg.org/magogi-admin/mgf-django).**

Extracted from mgf-common at v0.31.0 per ASPIRE-06 (federation split). The Django app label also rebranded from `mgf_common` to `mgf_django` at the cutover (Django labels can't contain dots). Consumers install the sibling directly:

```bash
uv add mgf-django                  # was: pip install 'mgf-common[django]'
```

See [mgf-django's PUBLIC_API.md](https://codeberg.org/magogi-admin/mgf-django/src/branch/main/PUBLIC_API.md) for the current public surface. Migration history is in `docs/cutover/v0.31.0.md`.

---

## `mgf.common.providers`

Provider seam ABC + translation decorator. No optional extras —
pure stdlib + `mgf.common.exceptions`.

| Name | Tier | Description |
|---|---|---|
| `ProviderABC` | **stable** (v0.32.3) | Abstract base for external-service provider adapters. One required method: `translate_exception(self, exc: Exception) -> AppError`. Implementers translate provider-SDK exceptions to typed `AppError` subclasses; the decorator handles the raise + cause-chain. |
| `translate_at_seam` | **stable** (v0.32.3) | Decorator. Wraps a method (sync or async). Lets `AppError` subclasses pass through unchanged; catches every other `Exception`, calls `self.translate_exception(exc)`, re-raises the typed `AppError` with the original as `__cause__`. Lets `BaseException` (KeyboardInterrupt, SystemExit, asyncio.CancelledError) pass through unchanged. Defensive: a `translate_exception` that returns non-`AppError` raises `TypeError` so buggy implementations fail loudly. Preserves wrapped-method metadata (`__name__`, `__doc__`) via `functools.wraps`. |

See [`docs/recipes/providers.md`](docs/recipes/providers.md)
for the full walkthrough including a worked example translating
provider-SDK exceptions, anti-patterns, and the duck-typed adoption
path (Protocol-based, no subclass required).

---

## `mgf.common.fastapi`

**Moved to sibling library [`mgf-fastapi`](https://codeberg.org/magogi-admin/mgf-fastapi).**

Extracted from mgf-common at v0.28.0 per ASPIRE-06 (federation split). This includes the previous `mgf.common.fastapi.webhooks`, `mgf.common.fastapi.security`, and `mgf.common.fastapi.testing` submodules, plus the FastAPI-shaped DB helpers (`get_session` / `setup_db`) that landed in `mgf.fastapi.db` at v0.30.0. Consumers install the sibling directly:

```bash
uv add mgf-fastapi                 # was: pip install 'mgf-common[fastapi]'
uv add 'mgf-fastapi[testing]'      # was: 'mgf-common[fastapi-test]'
uv add 'mgf-fastapi[sqlalchemy]'   # for the get_session / setup_db helpers
```

See [mgf-fastapi's PUBLIC_API.md](https://codeberg.org/magogi-admin/mgf-fastapi/src/branch/main/PUBLIC_API.md) for the current public surface. Migration history is in `docs/cutover/v0.28.0.md`.

---

## `mgf.common.http`

**Moved to sibling library [`mgf-http`](https://codeberg.org/magogi-admin/mgf-http).**

Extracted from mgf-common at v0.29.0 per ASPIRE-06 (federation split). At the cutover, `HttpClientError` was renamed to `HttpTransportError` to disambiguate from the HTTP-01 hierarchy parent (which keeps the `HttpClientError` name in `mgf.common.exceptions`). Consumers install the sibling directly:

```bash
uv add mgf-http                    # was: pip install 'mgf-common[http]'
uv add 'mgf-http[test]'            # was: 'mgf-common[http-test]'
```

See [mgf-http's PUBLIC_API.md](https://codeberg.org/magogi-admin/mgf-http/src/branch/main/PUBLIC_API.md) for the current public surface. Migration history is in `docs/cutover/v0.29.0.md`.

---

## `mgf.common.process`

Unified subprocess + service lifecycle. The structured replacement for raw `subprocess.run` and ad-hoc "launch this dev server, wait for the port, run my smoke test, kill it" bash scripts. Two top-level shapes: `run()` (one-shot) + `Service` (long-lived with readiness probe). POSIX-first; Windows is best-effort. No external deps.

| Name | Tier | Description |
|---|---|---|
| `run(command, *, cwd=None, env_extra=None, timeout=None, check=True, capture_output=True, name=None, redact=None, process_group=True, kill_grace_s=5.0) -> ProcessResult` | **stable** (v0.32.3) | One-shot subprocess wrapper. Streams child stdout (INFO) / stderr (WARNING) line-by-line through `logging.getLogger("mgf.process.<name>")` with `child_pid` / `stream` / `command_id` extras. `command` is list-only by construction (eliminates command-injection at the API surface). `redact=None` uses the `Redactor` wired by `bootstrap()` when active. `process_group=True` launches the child in its own session group + cascades SIGTERM via `os.killpg` on timeout. `check=True` (default) raises `ProcessNonZeroExitError` / `ProcessSignaledError` for non-zero / signal exits. |
| `ProcessResult(command, returncode, stdout, stderr, duration_s, command_id, pid)` | **stable** (v0.32.3) | Frozen dataclass returned by `run()`. Always populated regardless of returncode; callers using `check=False` inspect `returncode` directly. `command_id` is the 8-hex-char correlation id stamped on every captured log record. |
| `Service(command, *, ready, timeout_s=30.0, poll_interval_s=0.2, name=None, cwd=None, env_extra=None, process_group=True, kill_grace_s=5.0, redact=None)` | **stable** (v0.32.3) | Long-lived process with a readiness predicate + clean shutdown. Use as a context manager (`with Service(...) as svc: ...`) or via explicit `start()` / `stop()`. `start()` blocks until `ready.check()` returns True or `timeout_s` elapses; on timeout the service is fully torn down before `ServiceNotReadyError` is raised. Out of scope by design: restart-on-crash supervision, backoff, dependency ordering. |
| `Service.start()` / `Service.stop()` | **stable** (v0.32.3) | Idempotent. `start()` is no-op when already running; `stop()` is safe to call multiple times and after natural exit. **v0.22.0:** `start()` raises `ServiceNotReadyError` immediately (not after the timeout) when the child exits before becoming ready, with the returncode + stderr tail in `last_error`. |
| `Service.wait(timeout=None) -> int \| None` | **stable** (v0.32.3) | **v0.22.0.** Block until the child exits naturally OR `timeout` elapses. Returns `returncode` on natural exit, `None` on timeout (process still running). The escape hatch from `with Service(...)` for "let it run until it crashes / completes naturally" without `stop()` killing it. |
| `Service.is_running` / `Service.pid` / `Service.returncode` | **stable** (v0.32.3) | Properties. `returncode` is None while running; populated after stop() or natural exit. |
| `ReadinessProbe` | **stable** (v0.32.3) | Protocol — `check() -> bool` + `description: str`. Built-in implementations follow. Custom probes implement the protocol. |
| `PortReady(host, port, connect_timeout_s=1.0)` | **stable** (v0.32.3) | Ready when a TCP connection to `host:port` succeeds. Useful for Postgres / Redis / MySQL / Memcached. |
| `HttpReady(url, expected_status=200, request_timeout_s=2.0, headers={})` | **stable** (v0.32.3) | Ready when `urllib.urlopen(url)` returns the expected status. Stdlib-only — no `requests` / `httpx` dependency. For health endpoints (`/healthz`, `/-/ready`, `/_status`). |
| `LogLineReady(pattern, stream="both")` | **stable** (v0.32.3) | Ready when a regex matches a captured log line. Pair with `PortReady` for Postgres (`"database system is ready to accept connections"`). |
| `CallableReady(predicate, description_str="custom callable")` | **stable** (v0.32.3) | Escape hatch — arbitrary user-supplied `Callable[[], bool]`. |
| `AllOf(probes)` | **stable** (v0.32.3) | Composite — ready when every sub-probe is ready. Short-circuits on first False. |
| `AnyOf(probes)` | **stable** (v0.32.3) | Composite — ready when any sub-probe is ready. |

**Errors:** `ProcessError` + `ProcessTimeoutError` / `ProcessNonZeroExitError` / `ProcessSignaledError` / `ServiceNotReadyError` — see the `mgf.common.exceptions` Process-launch category above.

See [`docs/recipes/process-management.md`](docs/recipes/process-management.md) for the demo / test / script worked examples.

---

## `mgf.common.flags`

Feature flags — kill-switch + gradual-rollout primitive. Federation-wide standardisation of the env-var-feature-gate pattern every consumer was rolling its own version of. Stdlib-only default backend; Protocol shape so consumers swap to Redis / LaunchDarkly / ConfigCat without touching call sites. Audit emission on backend-configure per LG-04 + SC-17.

| Name | Tier | Description |
|---|---|---|
| `FeatureFlag` | **experimental** (v0.35.0) | Runtime-checkable Protocol — one method: `is_enabled(name, *, default, context)`. Backends implement this; consumers call it via the module-level `is_enabled` convenience function. |
| `EnvVarFlags()` | **experimental** (v0.35.0) | Default backend — reads `MGF_FLAG_<UPPER_NAME>` env vars per call (always-fresh, single `os.environ.get`). Truthy values: `1` / `true` / `yes` / `on` / `enabled`. Falsy: `0` / `false` / `no` / `off` / `disabled` / empty. Unrecognised values WARN + return the call-site default. |
| `StaticFlags(initial=None)` | **experimental** (v0.35.0) | In-memory backend for tests + scratch only. `.set(name, value)` + `.unset(name)` for fixture ergonomics. Thread-safe (single internal lock). |
| `configure_flags(impl)` | **experimental** (v0.35.0) | Install a new backend process-wide. Type-checked at runtime against `FeatureFlag`. Emits structured INFO with `event="mgf.flag_backend_configured"` + `audit=True` recording the previous + new backend names (SC-17 audit trail). |
| `get_flags() -> FeatureFlag` | **experimental** (v0.35.0) | Return the currently-installed backend (useful for test fixtures wanting `StaticFlags.set` after `configure_flags`). |
| `is_enabled(name, *, default=False, context=None) -> bool` | **experimental** (v0.35.0) | Module-level convenience — delegates to the installed backend. Default backend is `EnvVarFlags`. Does NOT audit on every call (per-call audit-on-read would flood logs). |

See module docstring for the full rationale, usage examples, and the per-consumer adoption guidance.

---

## `mgf.common.schedule`

Cron-expression parser + scheduler Protocol. Federation-wide standardisation of the cron-shape input that vm-vmanager-app (`croniter`), iosRecovery (systemd timer), and PlasmaMapper (`arq` cron syntax) each handle differently. Stdlib-only by design — no extra runtime deps. Three-consumer signal — the strongest evidence the federation has collected for any single primitive.

| Name | Tier | Description |
|---|---|---|
| `CronExpression` | **experimental** (v0.35.0) | Parsed cron expression. Frozen dataclass; field sets (`minutes`, `hours`, `days_of_month`, `months`, `days_of_week`) + the raw input. Construct via `CronExpression.parse(expr)`. |
| `CronExpression.parse(expr)` | **experimental** (v0.35.0) | Parse a standard 5-field expression (`minute hour dom month dow`) OR a canonical alias (`@yearly` / `@monthly` / `@weekly` / `@daily` / `@midnight` / `@hourly`). Supports `*`, `N`, `N-M`, `*/N`, `N-M/N`, comma-lists, and the POSIX day-of-week quirk (`7` = Sunday). Does NOT support `L` / `W` / `#` shortcuts or day-name / month-name aliases (defer to v1.1+ if a consumer needs them). |
| `CronExpression.matches(ts)` | **experimental** (v0.35.0) | Return True if the expression fires at the given **tz-aware** datetime (per DP-12). Naive datetimes raise `ValueError`. |
| `CronExpression.next_fire(*, after)` | **experimental** (v0.35.0) | Return the next tz-aware timestamp at-or-after `after` that matches. One-minute resolution. Raises `RuntimeError` if no match within 4 years (catches impossible combos like `0 0 31 2 *`). |
| `Scheduler` | **experimental** (v0.35.0) | Runtime-checkable Protocol — `schedule(expr, *, fn, name)` + `tick(*, now)`. Backends decide the execution model (in-process / async-runner / systemd-driven). |
| `ScheduledTask` | **experimental** (v0.35.0) | Frozen dataclass — `expr` / `fn` / `name` / `last_fired_at`. Returned by `Scheduler.schedule`. |
| `InProcessScheduler()` | **experimental** (v0.35.0) | Default tick-driven scheduler. Consumer's runner / event loop calls `tick()` periodically. Re-scheduling the same `name` REPLACES the previous task (idempotent registration). Catches exceptions in fired tasks (logs with `event="mgf.schedule.task_raised"`; never raises out of `tick`). Sub-minute tick calls are safe — each task fires at most once per minute. NOT suitable for multi-replica deployments without an external lock. |

See module docstring for supported cron syntax + per-consumer adoption guidance.

---

## `mgf.common.plugins`

Plugin auto-discovery — entry-points-based plugin loading. Closes PAPER-10 (deferred since 2026-04-XX; finally unblocked by PlasmaMapper's TelematicsProvider plugin shape). Wraps `importlib.metadata.entry_points` with Protocol-conformance filtering + audit-log emission.

| Name | Tier | Description |
|---|---|---|
| `PluginRecord(name, obj, distribution, version)` | **experimental** (v0.36.0) | Frozen dataclass for a successfully-loaded plugin entry. `obj` is whatever the entry point points at (function / class / instance). |
| `PluginRejection(name, distribution, reason, detail)` | **experimental** (v0.36.0) | Frozen dataclass for an entry that was found but NOT loaded. `reason` is `"load-failed"` or `"non-conformant"`. Only surfaces when `return_rejections=True`. |
| `discover(group, *, required=None, return_rejections=False)` | **experimental** (v0.36.0) | Discover + load every entry point in `group`. Optional Protocol filter (`required=`) rejects non-conforming entries with WARN logs. Optional `return_rejections=True` returns a `(records, rejections)` tuple for diagnostics. Audit emission per call (`event="mgf.plugin_discovery"`, `audit=True`). Load failures + non-conformant entries are SKIPPED, never propagated — one bad plugin can't crash the host startup. |

See module docstring for the full rationale (closes PAPER-10), usage examples, and the per-consumer adoption guidance.

---

## `mgf.common.doctor`

Doctor-bundle primitive — composable diagnostic-report scaffold. Promotes the existing `mgf-common doctor` CLI command's report-rendering logic into a reusable primitive. vm-vmanager-app + iosRecovery both wire this. Text + JSON renderers; 0o600 atomic-write per SC-13.

| Name | Tier | Description |
|---|---|---|
| `ReportStatus` | **experimental** (v0.36.0) | `Literal["ok", "warn", "fail"]` — the section status taxonomy. JSON-stable values. |
| `ReportSection(name, status, details={})` | **experimental** (v0.36.0) | Frozen dataclass — one section of a report. `details` is `Mapping[str, Any]`; sensitive values MUST be pre-redacted (the bundle does NOT scrub automatically — that would mask consumer bugs). `with_details_merged(more)` returns a copy with `more` merged. |
| `DoctorBundle(app_name, app_version, when=now)` | **experimental** (v0.36.0) | Composable bundle. `add_section(section)` appends OR replaces by name (idempotent compose). `sections()` returns insertion-order tuple. `overall_status` aggregates worst-status. |
| `DoctorBundle.render_text() -> str` | **experimental** (v0.36.0) | Human-readable text report. Designed for terminal + bug-report-paste. |
| `DoctorBundle.render_json() -> dict[str, Any]` | **experimental** (v0.36.0) | Machine-readable JSON-friendly dict. Stable schema (`schema_version: 1`). |
| `DoctorBundle.write_to(path, *, output_format="text")` | **experimental** (v0.36.0) | Atomic write to `path` with 0o600 mode per SC-13. `output_format` is `"text"` or `"json"`. Parent directory must exist (we never create arbitrary directory trees, per SC-13). |

See module docstring for the full rationale, usage examples, and the per-consumer adoption guidance.

---

## `mgf.common.rate_limit`

Rate-limit primitive — Wave 3 federation formalisation. Promotes the TokenBucket shape from sibling-local copies (mgf-fastapi 0.6.0 + mgf-django 0.4.0) into mgf-common; both siblings rebase on this module in their next minor (mgf-fastapi 0.7+, mgf-django 0.5+).

| Name | Tier | Description |
|---|---|---|
| `Bucket` | **experimental** (v0.37.0) | Runtime-checkable Protocol — one method: `try_consume(key) -> (allowed, retry_after_seconds)`. Future Redis-backed implementations satisfy this Protocol for production multi-replica deployments. |
| `TokenBucket(*, capacity, refill_per_second)` | **experimental** (v0.37.0) | Stdlib-only in-process token bucket. Per-key state; thread-safe. `.try_consume(key)` is the primitive; `.reset(key=None)` for test fixtures. NOT suitable for multi-replica without an external bucket-state store. |

---

## `mgf.common.retry`

Retry primitive — the canonical EH-11 resilience helper (ERROR_HANDLING §13.1). Exponential backoff + full jitter + finite cap + per-attempt `audit=true` log. Use ONLY for typed transient failures (timeouts, 503s, lock contention) — never for validation errors.

| Name | Tier | Description |
|---|---|---|
| `retry(fn, *, retryable=(AppError,), max_attempts=5, base_delay_s=0.5, max_delay_s=30.0, operation="<unnamed>")` | **experimental** (v0.48.0) | Call `fn` with exponential backoff + full jitter on failure; re-raises the last exception when the budget is exhausted. Only `retryable` exceptions are retried (everything else propagates immediately). Emits `event=retry.attempt` per retry + `event=retry.exhausted` on give-up (both `audit=true`). Full jitter: `uniform(0, min(max_delay_s, base_delay_s·2^(attempt-1)))`. |
| `aretry(fn, *, ...)` | **experimental** (v0.48.0) | Async analogue of `retry` — same MUSTs, `asyncio.sleep` backoff. `fn` returns an awaitable. |

---

## `mgf.common.io`

Filesystem helpers — the federation's crash-safe atomic write (CF-04). Promoted from the private `_io` so siblings adopt it instead of reaching into the closed-box surface (AP-14 / DP-13).

| Name | Tier | Description |
|---|---|---|
| `atomic_write(path, content, *, mode=0o600, fsync_dir=False, encoding="utf-8")` | **experimental** (v0.48.0) | Atomically write `content` (bytes or str) to `path`: tempfile in the same dir → `fsync` → `chmod` → atomic `os.replace`. Never leaves a partial/truncated file; tempfile cleaned up on any failure. `fsync_dir=True` makes the rename itself durable (POSIX-only). |

---

## `mgf.common.webhooks`

Framework-neutral Svix/Clerk webhook **HMAC verification** primitive (PAPER-26). The signature dance was hand-rolled identically in `mgf-auth` (Clerk credential verify) and `mgf-fastapi` (FastAPI transport adapter), and the copies drifted. Centralised here (the shared root) — crypto only, no provider semantics, no framework types. Consumers keep their value-add and delegate the compare here.

| Name | Tier | Description |
|---|---|---|
| `verify_svix_hmac(headers, body, secret, *, schema=SVIX_SCHEMA, replay_window_seconds=300, now=time.time) -> tuple[str, int]` | **experimental** (v0.49.0) | Verify a Svix-style webhook HMAC; returns `(msg_id, timestamp)`. Requires the 3 headers, enforces the replay window, computes `HMAC-SHA256("{id}.{ts}.{body}")` keyed by `decode_svix_secret`, constant-time compares every whitespace-separated `v1,<sig>` candidate (key rotation). Raises `WebhookHmacError` (machine-stable `.reason`) on any failure — fail-closed; never returns on a mismatch. |
| `decode_svix_secret(secret: bytes | str) -> bytes` | **experimental** (v0.49.0) | Return the raw HMAC key from a `whsec_<base64>` Svix secret (base64-decode after the prefix; a non-base64 dev secret falls back to its raw bytes — the verifier and any signer run the same function, so they stay consistent). |
| `WebhookHmacError` | **experimental** (v0.49.0) | Low-level, framework-neutral HMAC-verification failure carrying a machine-stable `.reason`; consumers map it to their own domain error (a 401, `HmacVerificationError`, …). |
| `WebhookHeaderSchema(id_header, timestamp_header, signature_header)` | **experimental** (v0.49.0) | Frozen dataclass naming the three Svix-style headers a provider uses (matched case-insensitively; stored lowercase). |
| `SVIX_SCHEMA: WebhookHeaderSchema` | **experimental** (v0.49.0) | The default Svix header names (`svix-id`/`svix-timestamp`/`svix-signature`); used directly by Clerk. |

---

## `mgf.common.idempotency`

Idempotency-key store — the retry-safe POST primitive. Closes the "double-charged retry" footgun class. Pairs with future middleware in mgf-fastapi v0.7+ + mgf-django v0.5+.

| Name | Tier | Description |
|---|---|---|
| `IdempotencyStore` | **experimental** (v0.37.0) | Runtime-checkable Protocol — `get(key, *, expected_body_hash)` + `put(key, *, body_hash, status_code, body, headers)`. |
| `IdempotencyRecord(key, body_hash, status_code, body, headers, created_at)` | **experimental** (v0.37.0) | Frozen dataclass — what `get` returns on hit. |
| `InMemoryIdempotencyStore(*, ttl_seconds=86400)` | **experimental** (v0.37.0) | Default backend. TTL-bounded (24h Stripe convention); thread-safe; lazy eviction. Multi-replica deployments wire a Redis-backed impl. |
| `IdempotencyKeyConflictError(*, key, expected_body_hash, actual_body_hash)` | **experimental** (v0.37.0) | Raised when the same key is presented with a different body — the canonical Stripe-style "would silently succeed with the wrong response" prevention. |

---

## `mgf.common.health`

Health-check primitive — liveness + readiness + aggregation. Standardises the shape every web consumer hand-rolls; framework siblings (mgf-fastapi `HealthRouter` v0.7+; mgf-django `health_view` v0.5+) wire the routes.

| Name | Tier | Description |
|---|---|---|
| `HealthStatus` | **experimental** (v0.37.0) | `Literal["ok", "degraded", "unhealthy"]`. JSON-stable. |
| `ComponentCheck` | **experimental** (v0.37.0) | Runtime-checkable Protocol — `.name` property + `.check() -> ComponentResult`. Implementations decide their internals (DB ping, TCP connect, internal-state lookup). |
| `ComponentResult(name, status, message, latency_ms)` | **experimental** (v0.37.0) | Frozen dataclass — one check's outcome. |
| `LivenessCheck` | **experimental** (v0.37.0) | Subclass marker — registry routes these into `/healthz` ("orchestrator: restart on fail"). |
| `ReadinessCheck` | **experimental** (v0.37.0) | Subclass marker — registry routes these into `/readyz` ("orchestrator: route around on fail"). |
| `HealthReport(status, components)` | **experimental** (v0.37.0) | Frozen dataclass — aggregated outcome. Worst-case status aggregation: `unhealthy` ≻ `degraded` ≻ `ok`. `.render_json()` for the standard wire shape. |
| `HealthRegistry()` | **experimental** (v0.37.0) | Aggregates ComponentChecks. `.add(check)` / `.remove(name)`; `.evaluate_liveness()` / `.evaluate_readiness()` / `.evaluate_all()`. Auto-populates latency. Catches per-check exceptions + reports as `unhealthy`. |

---

## `mgf.common.cache`

Cache abstraction — Protocol + LRU/Null defaults. Standardises the cache surface so consumers can swap LRU → Redis → Memcached without touching call sites.

| Name | Tier | Description |
|---|---|---|
| `Cache` | **experimental** (v0.37.0) | Runtime-checkable Protocol — `get` / `set` / `delete` / `clear`. Five-method surface. |
| `CacheMiss` | **experimental** (v0.37.0) | Sentinel returned by `Cache.get` for absent keys. Distinguishable from cached `None`. Falsy; check via `is CacheMiss`. |
| `LRUCache(*, maxsize=1024, default_ttl_seconds=None)` | **experimental** (v0.37.0) | Stdlib-only thread-safe LRU + TTL cache. `OrderedDict`-backed. Per-entry or default TTL; expired entries evicted lazily on access. |
| `NullCache()` | **experimental** (v0.37.0) | No-op cache — every `get` is a miss, every `set` dropped. Useful as the "disable caching" toggle in tests. |

---

## Notes on the namespace package

`mgf` is a [PEP 420](https://peps.python.org/pep-0420/) implicit
namespace package. There is intentionally **no `__init__.py`** at
`src/mgf/` so future siblings (`mgf.cli`, `mgf.gui`) from other
repos can contribute their own subpackages under the same `mgf.*`
namespace. The hallmark: `import mgf; mgf.__file__` is `None`.

This shape is part of the public API in the sense that consumers
who want to add their own `mgf.<sibling>` module rely on it
working. It is pinned by `tests/unit/test_identity.py::test_namespace_package_shape`.

---

*See [`docs/standards/API_DESIGN.md`](docs/standards/API_DESIGN.md)
for the standard that defines what every column in this document
means.*
