# 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` | experimental | 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` | experimental | Returns the active `AppContext` (per-task contextvar OR module-global) or `None` if `bootstrap()` has not been called. Thread-safe; ~30 ns per call. |
| `AppContext` | experimental | 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). |
| `configure(app_name: str, app_version: str = "unknown") -> None` | deprecated | Sets the process-wide app identity ONLY (no logging / OTel / excepthook wiring). Use `bootstrap()` instead — it wires identity + logging + OTel + crash hooks transactionally with LIFO rollback. First call emits `MgfDeprecationWarning`. |
| `app_name() -> str` | experimental | 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` | experimental | Read the active app version. Same resolution order as `app_name`. |
| `UnconfiguredWarning` | experimental | `UserWarning` subclass emitted once per process when identity is read before `configure()` / `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` | experimental | Test-friendly bootstrap. Thin wrapper over `bootstrap()` with `MgfSettings.testing()` defaults — WARNING-level logging, OTel disabled, deterministic preset. Pair with pytest fixtures. See `docs/public/library_common.md` §3. |
| `add_help_note(exc, hint, *, doc_ref="")` | experimental | 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. |

---

## `mgf.common.exceptions`

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

### Base

| Name | Tier | Description |
|---|---|---|
| `AppError` | experimental | Base for every exception raised by `mgf.common` and consumer projects. Subclass to extend. |

### Category bases (7)

| Name | Tier | Description |
|---|---|---|
| `ConfigError` | experimental | Configuration / spec validation failures |
| `ProviderError` | experimental | Failures inside a provider implementation (third-party SDK boundary) |
| `OperationError` | experimental | Runtime failures during a long-running operation |
| `GuestError` | experimental | Failures communicating with a remote agent (SSH, agent, health probe) |
| `EnvironmentError` | experimental | Host pre-flight failures (deliberately shadows the builtin alias — see `FEEDBACK.md`) |
| `VaultError` | experimental | Credential vault failures |
| `SchedulerError` | experimental | Scheduler / hook subsystem failures |

### Generic concretes (7)

| Name | Tier | Description |
|---|---|---|
| `SchemaValidationError(errors: list[str])` | experimental | Carries `.errors`; message: `"validation failed: ..."` |
| `AppConfigError` | experimental | 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` | experimental | `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)` | experimental | Carries `.argv` / `.returncode` / `.stderr`; subprocess wrapper translation target |
| `ResourceNotFoundError(name: str)` | experimental | Carries `.name` |
| `ResourceAlreadyExistsError(name: str)` | experimental | Carries `.name` |
| `GuestUnreachableError` | experimental | Bare subclass; message in args |
| `GuestCommandError` | experimental | Bare subclass; message in args |
| `BootstrapError` | experimental | Subclass of `OperationError`. Raised by `bootstrap()` when a wiring step fails; carries `.failed_step` + chained cause.2 (transactional bootstrap). |
| `HostEnvironmentError` | experimental | Renamed from `EnvironmentError` to stop shadowing Python's built-in alias for `OSError`. The legacy `EnvironmentError` name remains as a deprecated alias (emits `MgfDeprecationWarning` on instantiation). |
| `CancellationError` | experimental | 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` | experimental | Base for web/API exceptions. Default `http_status=500`. Three-level category-catch property: `except HttpError:` / `except HttpClientError:` (4xx) / `except HttpServerError:` (5xx). |
| `HttpClientError` | experimental | Base for 4xx — request was bad. `http_status=400`. |
| `HttpServerError` | experimental | Base for 5xx — server failed to fulfill a valid request. `http_status=500`. |
| `HttpBadRequest` | experimental | 400 — generic client-side validation / parse failure. |
| `HttpUnauthorized` | experimental | 401 — authentication required (no / invalid credentials). |
| `HttpForbidden` | experimental | 403 — authenticated but not permitted. |
| `HttpNotFound` | experimental | 404 — resource doesn't exist. Distinct from `ResourceNotFoundError` (provider-domain). |
| `HttpConflict` | experimental | 409 — request conflicts with current state. |
| `HttpGone` | experimental | 410 — resource existed but is permanently removed. |
| `HttpUnprocessable` | experimental | 422 — request well-formed but semantically invalid. The canonical "validation failed" status for web APIs. |
| `HttpInternalServerError` | experimental | 500 — generic server-side failure. |
| `HttpServiceUnavailable` | experimental | 503 — temporarily unavailable. Set `Retry-After` on the response. |

### 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` | experimental | Base for `mgf.common.process` failures. Subclass of `OperationError`. |
| `ProcessTimeout` | experimental | Child exceeded the `timeout=` deadline. Carries `.timeout: float` (seconds). |
| `ProcessNonZeroExit` | experimental | 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). |
| `ProcessSignaled` | experimental | Child was terminated by a signal. POSIX-only failure mode. Carries `.signal: int`. |
| `ServiceNotReady` | experimental | 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__`: 36 names** (`EnvironmentError` is the deprecated alias — counted but emits warning).

---

## `mgf.common.config`

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

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

### Pydantic re-exports

| Name | Tier | Description |
|---|---|---|
| `BaseModel` | experimental | Re-exported `pydantic.BaseModel` for nested sub-model definitions inside `Settings` classes. |
| `ConfigDict` | experimental | Re-exported `pydantic.ConfigDict` for inner `model_config = ConfigDict(extra="forbid")` declarations on sub-models. |
| `Field` | experimental | Re-exported `pydantic.Field` for typed-field declarations with constraints (`Field(default=30, ge=1, le=600)`) and aliases. |
| `AliasChoices` | experimental | Re-exported `pydantic.AliasChoices` for renames-without-breaks (rule CF-11). |
| `field_validator` | experimental | Re-exported `pydantic.field_validator` for single-field validators. |
| `model_validator` | experimental | Re-exported `pydantic.model_validator` for cross-field / whole-model validators. |
| `ValidationError` | experimental | 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 |
|---|---|---|
| `setup_logging(*, level="INFO", fmt="auto", log_file=None, redactor=None) -> None` | experimental | Single-point logging configuration. Console + rotating file by default; opt-in journald / OTLP / syslog / HTTP via env vars |
| `setup_otel(*, enabled=None) -> None` | experimental | Initialise the OpenTelemetry SDK + OTLP span exporter. No-op when SDK not installed or no endpoint configured |
| `operation_span(name: str, **attrs) -> ContextManager[None]` | experimental | Context manager wrapping a use case in a span. Safe to use even when OTel is disabled |
| `report_now(exc, *, source: str) -> Path` | experimental | Build, persist locally, and best-effort ship a crash report. Returns the local file path. NEVER raises |
| `install_excepthook() -> None` | experimental | Process-wide unhandled-exception hook; idempotent |
| `install_threading_excepthook() -> None` | experimental | Non-main-thread excepthook; idempotent |
| `Redactor` | experimental | Sensitive-field redaction policy used by the formatters. Frozen dataclass; thread-safe. Composes via `\|`. |
| `DEFAULT_REDACTION_KEYS` | experimental | The built-in secret-bearing key set, exposed as `frozenset[str]` so consumers can audit / extend without copy-paste. |
| `LogFormat` | experimental | `Literal["text", "json", "auto"]` — the typed alias used by `setup_logging` |

### path helpers

| Name | Tier | Description |
|---|---|---|
| `default_log_path() -> Path` | experimental | `<state>/<app>/logs/<app>.log` — where `setup_logging` writes when called without an explicit `log_file=` override. Lazy (resolves at call time, rule CF-04). Surface in UI for "where do my logs live?". |
| `default_crash_dir() -> Path` | experimental | `<state>/<app>/crashes/` — where `report_now` writes serialised crash reports. Lazy. Surface in UI for crash-report browsing. |
| `default_state_dir() -> Path` | experimental | `<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` | experimental | Returns `logging.getLogger(name)` exposed through us so consumers don't `import logging`. |
| `get_current_span() -> Span | None` | experimental | Returns the active OTel `Span` or `None`. No-op-safe when OTel disabled. |
| `set_attribute(key, value) -> None` | experimental | Set an attribute on the current span. No-op when no span active. |
| `add_event(name, attributes=None, timestamp=None) -> None` | experimental | Add a named event to the current span. No-op when no span active. |
| `set_status(status, description="") -> None` | experimental | Set the current span's status (typically `SpanStatus(StatusCode.ERROR, "...")`). No-op when no span active. |
| `Span` | experimental | Re-exported `opentelemetry.trace.Span` (or `Any` sentinel when OTel not installed). |
| `SpanContext` | experimental | Re-exported `opentelemetry.trace.SpanContext`. |
| `SpanKind` | experimental | Re-exported `opentelemetry.trace.SpanKind`. |
| `SpanStatus` | experimental | Re-exported `opentelemetry.trace.Status` — renamed at our boundary because the bare name `Status` collides with too many other things in consumer code. |
| `StatusCode` | experimental | Re-exported `opentelemetry.trace.StatusCode` — values `OK` / `ERROR` / `UNSET` for span status. |

### registries

| Name | Tier | Description |
|---|---|---|
| `register_crash_sink(name)` | experimental | 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)` | experimental | Remove a registered crash sink. No-op if unknown. |
| `CrashSink` | experimental | Type alias: `Callable[[Path, dict[str, Any]], None]` — the crash-sink handler signature. |
| `register_log_handler(name)` | experimental | Decorator: register a custom log-handler factory. Activated via `MgfSettings.logging.extra_handlers` (full wiring lands in a later phase). |
| `unregister_log_handler(name)` | experimental | Remove a registered log handler. |
| `LogHandlerFactory` | experimental | Type alias: `Callable[[MgfSettings], logging.Handler]`. |
| `register_redaction_pattern_group(name)` | experimental | 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)` | experimental | Remove a registered pattern group. |
| `PatternGroupFactory` | experimental | Type alias: `Callable[[], tuple[re.Pattern[str], ...]]`. |
| `register_span_processor(name)` | experimental | Decorator: register a custom OTel span-processor factory. Activated via `MgfSettings.otel.enabled_processors` (full wiring lands in a later phase). |
| `unregister_span_processor(name)` | experimental | Remove a registered span processor. |
| `SpanProcessorFactory` | experimental | Type alias: `Callable[[MgfSettings], SpanProcessor]`. |
| `RedactionGroup` | experimental | `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` | experimental | 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` | experimental | 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)`. |

**Total in `mgf.common.observability.__all__`: 37 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` | experimental | 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` | experimental | 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` | experimental | OTel fields: enabled / endpoint / service_namespace / sample_rate / resource_attributes / httpx_instrumented / subprocess_instrumented / metrics_enabled. |
| `CrashSettings` | experimental | Crash-reporter fields: sinks / sentry_dsn / otlp_endpoint / http_url / state_dir. will introduce the registry. |
| `RedactionSettings` | experimental | Redactor fields: extra_keys / extra_value_patterns / enabled_pattern_groups / strict_mode.12 (GDPR strict mode — wires the patterns). |
| `LogLevel` | experimental | `Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]` — stable type alias. |
| `LogFormat` | experimental | `Literal["text", "json", "auto"]` — moved here from `mgf.common.observability` (legacy alias still works). |
| `PresetName` | experimental | `Literal["explicit", "dev", "systemd", "docker", "auto"]` — preset selector. ships the seam; wires the preset semantics. |

| `VaultSettings` | experimental | 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` | experimental | Cooperative-cancellation contract: `cancel() -> None` + `is_cancelled() -> bool`. Concrete implementations: `CancellationToken` (sync) + `AsyncCancellationToken` (async). `runtime_checkable` so `isinstance(obj, CancellationProtocol)` works. |
| `ReporterProtocol` | experimental | 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` | experimental | 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` | experimental | 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 VManager. of the
redesign.

| Name | Tier | Description |
|---|---|---|
| `CancellationToken` | experimental | Thread-safe sync cancellation token. `cancel() / is_cancelled() / wait(timeout) / raise_if_cancelled()`. Backed by `threading.Event`. Implements `CancellationProtocol`. |
| `ProgressReporter` | experimental | Ergonomic alias for `mgf.common.typing.ReporterProtocol`. |
| `NullReporter` | experimental | No-op concrete impl of `ProgressReporter`. Optionally takes a `token=` parameter to forward `is_cancelled()` to a real cancellation token. |
| `RecordingReporter` | experimental | 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` | experimental | 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` | experimental | 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` | experimental | 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` | experimental | 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)` | experimental | 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)` | experimental | Remove a registered vault backend. No-op if unknown. |
| `VaultBackendFactory` | experimental | 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_")` | experimental | 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")` | experimental | 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)` | experimental | Typed config for the built-in `"file"` backend. Both fields are required (pydantic enforces — vs the runtime check in the factory). |
| `BuiltinVaultBackendConfig` | experimental | `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)` | experimental | 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` | experimental | `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` | experimental | `0` — clean exit. |
| `EXIT_CONFIG_ERROR` | experimental | `1` — `ConfigError` family. |
| `EXIT_OPERATION_ERROR` | experimental | `2` — `OperationError` family (includes `CancellationError` and `BootstrapError` by default). |
| `EXIT_GUEST_ERROR` | experimental | `3` — `GuestError` family. |
| `EXIT_ENVIRONMENT_ERROR` | experimental | `4` — `HostEnvironmentError` family. |
| `EXIT_PROVIDER_ERROR` | experimental | `5` — `ProviderError` family (includes `ResourceNotFoundError` / `ResourceAlreadyExistsError`). |
| `EXIT_VAULT_ERROR` | experimental | `6` — `VaultError` family. |
| `EXIT_UNKNOWN` | experimental | `10` — untyped 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` | experimental | 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")` | experimental | 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")` | experimental | 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]]` | experimental | 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]]` | experimental | 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` | experimental | 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` | experimental | `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) | experimental | Function-scoped fresh `MgfSettings.testing()` instance. |
| `mgf_context` (fixture) | experimental | Function-scoped bootstrapped `AppContext` (auto-shutdown LIFO). |
| `mgf_context_with_capture` (fixture) | experimental | 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) | experimental | Installs an SDK `TracerProvider` once per session. Skips if `[observability]` extra is missing. Defers to an already-installed SDK provider. |
| `redaction_recorder` (fixture) | experimental | Records every `Redactor.redact` call's pre/post payload for assertion. Function-scoped (resets per test). |
| `decode_json_logs(caplog) -> list[dict]` | experimental | 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/public/recipes/testing.md`](docs/public/recipes/testing.md) for
the walkthrough.

---

## Grand totals

| Package | Public names |
|---|---|
| `mgf.common` | 10 (9 in `__all__` + `__version__`) |
| `mgf.common.exceptions` | 36 |
| `mgf.common.config` | 16 |
| `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.cli` | 21 |
| `mgf.common.registry` | 2 |
| `mgf.common.pytest_plugin` | 6 |
| `mgf.common.http` | 3 |
| `mgf.common.fastapi` | 9 |
| `mgf.common.db` | 5 |
| `mgf.common.alembic` | 1 |
| `mgf.common.django` | 5 |
| `mgf.common.providers` | 2 |
| `mgf.common.observability` | 37 |
| `mgf.common.process` | 10 |
| **Total** | **193** (10 + 36 + 16 + 9 + 4 + 4 + 1 + 10 + 2 + 21 + 2 + 6 + 3 + 9 + 5 + 1 + 5 + 2 + 37 + 10 = 193 ✓ verified against `PUBLIC_API.json`'s live `__all__` introspection (introspected from the source tree) |

---

## 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 (VManager) 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`

Async SQLAlchemy session manager + tenant-scoping helper. Optional
extra: `pip install 'mgf-common[db]'`. Without the extra, the
subpackage cannot be imported. Production consumers also need an
async driver (asyncpg / aiomysql / etc.) — we don't pin one.

| Name | Tier | Description |
|---|---|---|
| `create_engine(database_url, *, echo=False, pool_pre_ping=True, pool_recycle=3600, pool_size=5, max_overflow=10, **extra)` | experimental | Builds an `AsyncEngine` from a SQLAlchemy URL. Production-leaning defaults (pre-ping on, 1h recycle). SQLite URLs auto-skip pool kwargs (StaticPool/NullPool reject them). `**extra` forwards to `create_async_engine`. |
| `create_sessionmaker(engine, *, expire_on_commit=False)` | experimental | Wraps `async_sessionmaker` with `expire_on_commit=False` (SQLAlchemy 2 async-recommended default). Override via kwarg. |
| `get_session(request) -> AsyncIterator[AsyncSession]` | experimental | FastAPI-Depends-compatible async generator. Reads `request.app.state.mgf_db_sessionmaker`; yields one session per request; closes via the context manager. Commit/rollback is the consumer's responsibility. Raises `AppConfigError` if the sessionmaker isn't wired (missing `setup_db`). |
| `tenant_session(session, tenant_id) -> AsyncIterator[AsyncSession]` | experimental | Context manager that runs `SET LOCAL app.current_tenant = '<uuid>'` for Postgres RLS-based multi-tenancy (PlasmaMapper rule PM-MT-04). LOCAL scopes to the current transaction; auto-resets on commit/rollback. UUID-validated input prevents SQL injection (Postgres `SET LOCAL` can't accept bind params). |
| `setup_db(app, *, database_url, **engine_kwargs) -> AsyncContextManager[None]` | experimental | FastAPI lifespan helper. Creates engine + sessionmaker; stashes on `app.state.mgf_db_engine` / `app.state.mgf_db_sessionmaker`; disposes engine pool on lifespan exit. Composable with `mgf.common.bootstrap()` inside the same lifespan. |

See [`docs/public/recipes/db.md`](docs/public/recipes/db.md) for the
full walkthrough including Postgres RLS setup, manual session
patterns, and the full-service composition.

---

## `mgf.common.alembic`

Alembic `env.py` helper that replaces the ~40-line boilerplate every
async-SQLAlchemy + alembic project repeats. Optional extra:
`pip install 'mgf-common[alembic]'` (pulls `alembic>=1.13`). Without
the extra, the subpackage cannot be imported.

| Name | Tier | Description |
|---|---|---|
| `configure_env(*, database_url, target_metadata, compare_type=True, compare_server_default=True, naming_convention=None, include_object=None, include_schemas=False) -> None` | experimental | Run alembic migrations from `env.py` with one call. Auto-detects async drivers (`+asyncpg`, `+aiomysql`, `+aiosqlite`, `+psycopg` async, `+asyncmy`) by URL prefix and runs them via `asyncio.run` + `connection.run_sync`; sync drivers take the synchronous path. Detects offline mode (`alembic upgrade --sql ...`) via `context.is_offline_mode()`. Applies `naming_convention` to `target_metadata` if supplied. Forwards `compare_type` / `compare_server_default` / `include_object` / `include_schemas` to `context.configure`. **`include_object`** (v0.19.0): `(obj, name, type_, reflected, compare_to) -> bool` filter to skip Postgres extension tables (PostGIS `spatial_ref_sys`, TimescaleDB internal, etc.) from autogenerate. **`include_schemas`** (v0.19.0): let autogenerate see non-default schemas. Defaults match alembic's own. Closes PAPER-22. Disposes the engine on exit. |

See [`docs/public/recipes/alembic.md`](docs/public/recipes/alembic.md)
for the full walkthrough including settings-driven URL, multi-database
patterns, and naming-convention recommendations.

---

## `mgf.common.django`

Django integration adapters. Optional extra:
`pip install 'mgf-common[django]'` (pulls `django>=4.2`). Without
the extra, the subpackage cannot be imported.

| Name | Tier | Description |
|---|---|---|
| `MgfCommonConfig` | experimental | Django `AppConfig` (label=`mgf_common`). Calls `bootstrap()` exactly once per process via a module-level guard; reads `MGF_COMMON_APP_NAME` / `MGF_COMMON_APP_VERSION` from `django.conf.settings`. Holds the bootstrap context manager open for the process lifetime; `atexit` hook unwinds it cleanly so OTel flushes + log handlers detach. Add `"mgf.common.django"` to `INSTALLED_APPS`. |
| `MgfRequestIdMiddleware` | experimental | Django middleware. Per-request `X-Request-Id` injection / forwarding (case-insensitive header lookup; UUID4 if absent). Sets `request.mgf_request_id` AND the same contextvar `mgf.common.fastapi.RequestIdMiddleware` uses (so cross-framework processes correlate end-to-end). Echoes in response. |
| `MgfContextMiddleware` | experimental | Django middleware. Reads `current_context()` and stashes the active `AppContext` on `request.mgf_context`. Defensive: returns `None` rather than raising if `MgfCommonConfig` hasn't run yet. Install AFTER `MgfRequestIdMiddleware`. |
| `JsonFormatter` | experimental | No-arg subclass of `mgf.common.observability.JsonFormatter` for Django's `LOGGING['formatters']` dict. Use as `{"()": "mgf.common.django.JsonFormatter"}`. Wires the default `Redactor` automatically (consumers don't need to construct one). |
| `DjangoSettingsBridge` | experimental | Read-only `BaseAppSettings` snapshot of `django.conf.settings`. Exposes a curated subset (DEBUG / ALLOWED_HOSTS / INSTALLED_APPS / TIME_ZONE / USE_TZ / LANGUAGE_CODE / SECRET_KEY-presence-only / MGF_COMMON_APP_NAME / MGF_COMMON_APP_VERSION). The actual `SECRET_KEY` is NEVER carried — only a presence boolean — so `mgf-common explain` output is safe. Construct via `DjangoSettingsBridge.from_django()`. |

See [`docs/public/recipes/django.md`](docs/public/recipes/django.md)
for the full walkthrough including the canonical `INSTALLED_APPS` /
`MIDDLEWARE` / `LOGGING` integration shape.

---

## `mgf.common.providers`

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

| Name | Tier | Description |
|---|---|---|
| `ProviderABC` | experimental | 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` | experimental | 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/public/recipes/providers.md`](docs/public/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`

FastAPI integration adapters. Optional extra:
`pip install 'mgf-common[fastapi]'`. Without the extra, the
subpackage cannot be imported.

| Name | Tier | Description |
|---|---|---|
| `setup_lifespan(*, app_name=None, app_version=None, settings=None)` | experimental | Build a FastAPI lifespan that runs `mgf.common.bootstrap()` at app startup. The resulting `AppContext` is stashed on `app.state.mgf_context` for dependency injection. LIFO unwind on shutdown. |
| `RequestIdMiddleware` | experimental | ASGI middleware. Injects/forwards `X-Request-Id` header (case-insensitive); generates UUID4 if absent. Sets `request.state.request_id` AND a contextvar. Echoes in response. |
| `current_request_id() -> str` | experimental | Read the per-request id from the contextvar. Returns empty string outside a request. PEP 567 propagates across async children. |
| `REQUEST_ID_HEADER = "X-Request-Id"` | experimental | Canonical header name. Inbound case-insensitive; outbound canonical. |
| `ExceptionTranslationMiddleware` | experimental | ASGI middleware. Catches `AppError` from handlers; maps to typed JSON responses via `status_map` (default per category). Configurable via `status_map=` and `response_factory=` kwargs. Includes `request_id` in error body when `RequestIdMiddleware` is installed. |
| `DEFAULT_STATUS_MAP: dict[type[AppError], int]` | experimental | Default exception → HTTP status mapping. `SchemaValidationError`→400, `ResourceNotFoundError`→404, `ResourceAlreadyExistsError`→409, `HostEnvironmentError`→503, `ConfigError`/`OperationError`/`VaultError`/`AppError`→500. |
| `get_app_context(request) -> AppContext` | experimental | FastAPI dependency. Reads `request.app.state.mgf_context`; raises `AppConfigError` if unset (lifespan not wired). |
| `get_settings(request) -> MgfSettings` | experimental | FastAPI dependency. Returns the `MgfSettings` from the active context. |
| `get_request_id(request) -> str` | experimental | FastAPI dependency. Reads `request.state.request_id`; returns `""` if `RequestIdMiddleware` is not installed. |

See [`docs/public/recipes/fastapi.md`](docs/public/recipes/fastapi.md)
for the full walkthrough including custom mappings, custom response
factories, and integration with `mgf.common.http`.

---

## `mgf.common.http`

Async HTTP client wrapper. Optional extra: `pip install 'mgf-common[http]'`.
Without the extra, the subpackage cannot be imported.

| Name | Tier | Description |
|---|---|---|
| `AsyncHttpClient(*, base_url="", timeout=30.0, verify_tls=True, user_agent=None, default_headers=None, retry=None)` | experimental | Wraps `httpx.AsyncClient` with mgf-common defaults: User-Agent auto-derived from app identity, retry-on-transient-failure with exponential-backoff-plus-jitter, secret-header redaction in logs, TLS-verify default-on with `UserWarning` on disable, typed error translation, best-effort OTel client span. |
| `AsyncHttpClient.request(method, url, *, headers=None, params=None, json=None, content=None, timeout=None, retry=None)` | experimental | Send a request. 4xx/5xx return as `httpx.Response`; transport-level failures raise `HttpClientError`. Per-call `timeout` and `retry` override constructor defaults. |
| `AsyncHttpClient.get/post/put/patch/delete/head` | experimental | Convenience shortcuts forwarding to `request(METHOD, url, ...)`. |
| `AsyncHttpClient.aclose()` / context-manager protocol | experimental | Close the connection pool. Idempotent. |
| `AsyncHttpClient.client` | experimental | Property exposing the wrapped `httpx.AsyncClient` for httpx-specific features. Bypasses retry + redaction + error translation. |
| `RetryPolicy(max_attempts=0, base_backoff_seconds=0.5, max_backoff_seconds=30.0, retryable_methods=..., retryable_statuses={429,503,504})` | experimental | Frozen retry config. Default: zero retries. Defaults exclude POST/PATCH (non-idempotent). |
| `HttpClientError` | experimental | Subclass of `OperationError`. Carries `url`, `cause_summary`, `attempts`. Original `httpx` exception preserved via `__cause__`. Routes to `EXIT_OPERATION_ERROR` via `mgf.common.cli.run_and_translate`. |

See [`docs/public/recipes/http.md`](docs/public/recipes/http.md) for
the full walkthrough.

---

## `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` | experimental | 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 `ProcessNonZeroExit` / `ProcessSignaled` for non-zero / signal exits. |
| `ProcessResult(command, returncode, stdout, stderr, duration_s, command_id, pid)` | experimental | 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)` | experimental | 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 `ServiceNotReady` is raised. Out of scope by design: restart-on-crash supervision, backoff, dependency ordering. |
| `Service.start()` / `Service.stop()` | experimental | Idempotent. `start()` is no-op when already running; `stop()` is safe to call multiple times and after natural exit. **v0.22.0:** `start()` raises `ServiceNotReady` 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` | experimental | **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` | experimental | Properties. `returncode` is None while running; populated after stop() or natural exit. |
| `ReadinessProbe` | experimental | Protocol — `check() -> bool` + `description: str`. Built-in implementations follow. Custom probes implement the protocol. |
| `PortReady(host, port, connect_timeout_s=1.0)` | experimental | 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={})` | experimental | 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")` | experimental | 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")` | experimental | Escape hatch — arbitrary user-supplied `Callable[[], bool]`. |
| `AllOf(probes)` | experimental | Composite — ready when every sub-probe is ready. Short-circuits on first False. |
| `AnyOf(probes)` | experimental | Composite — ready when any sub-probe is ready. |

**Errors:** `ProcessError` + `ProcessTimeout` / `ProcessNonZeroExit` / `ProcessSignaled` / `ServiceNotReady` — see the `mgf.common.exceptions` Process-launch category above.

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

---

## Notes on `EnvironmentError`

`mgf.common.exceptions.EnvironmentError` deliberately shadows the
Python builtin alias. This is documented in the source with a
`# noqa: A001` and is one of the 8 🔴 Blockers in
[`FEEDBACK.md`](FEEDBACK.md) that need decision. The likely resolution is to rename to
`HostEnvironmentError` before v1.0; consumers will get a deprecation
alias for one MINOR cycle.

## 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.*
