# Changelog

All notable changes to `mgf-cloud` are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) within the 0.x window
(rule **AP-03** — pin one minor at a time).

## [0.1.2] - 2026-06-25

### Changed
- Adopt **mgf-common 0.48** — widen the pin to `<0.49` (the `retry` + public
  `io.atomic_write` primitives + the L3 cornerstone; cohort pin-walk for the
  0.48 minor).

### Added
- **Operability verbs (OP-01/OP-02/OP-05/OP-07).** Four new offline diagnostic
  commands: `mgf-cloud version` is now rich (package version + git SHA + python +
  platform + dep versions; `--json`); `mgf-cloud doctor` checks install + config
  health without a live cloud call (provider registry, provider-SDK extra,
  credential resolution/parse, rates.toml parse) and exits with the failed-check
  count; `mgf-cloud explain` prints the resolved `CloudConfig` + which source won
  the resolution race (secrets stay `SecretStr`-redacted); `mgf-cloud bundle`
  writes a single redacted diagnostic tarball (version + explain + doctor + log
  tail + crash reports, each section fail-open + `Redactor`-scrubbed). Built on
  `mgf.common.doctor.DoctorBundle` + `mgf.common.observability.Redactor`.
- **Observability wiring (OB-01/LG-02/LG-06/LG-11).** `mgf.common.bootstrap` now
  wires the federation logging stack at both entry points (CLI `main`, web
  `serve`); per-module `get_logger`; INFO operation start/end with a stable
  `operation` field + `duration_ms` at the Infomaniak `_boundary` and
  `remote.run_commands`.
- **Audit records (SC-17).** `audit=true` structured events at the clean seams:
  `ssh.connect`/`ssh.disconnect`, `auth.ok`/`auth.fail` — carrying host/user/
  auth_url but never the secret. A unit test per event.
- **Top-level crash handling (EH-04).** The CLI routes an untyped bug to a crash
  report + an actionable message instead of a raw traceback.

### Changed
- **Single-sourced `__version__` (AP-13/REL-03/REL-06/OP-04).** Read from the
  installed package metadata so it can never drift from `pyproject.toml`; pinned
  by `tests/unit/test_version_in_sync.py`. Regenerated `PUBLIC_API.json` and
  enumerated every public name in `PUBLIC_API.md` (Tier + Since); refreshed the
  README status off the stale "Pass-N scaffold" framing.
- **Typed errors (EH-01).** `cost.rates_from_config` raises `ConfigError` (was a
  raw `FileNotFoundError`).
- **Finite REST timeout (SC-07).** `InfomaniakProvider` threads a configurable
  `api_timeout` (default 30s) into the OpenStack `Connection`.

### Fixed
- **Idempotent volume create (EH-12).** `create_volume` rejects a duplicate name
  (Infomaniak adapter + `FakeProvider`) so a retry can't leak a billable volume.

### Security
- Documented in-tree triage for the suppressed CVEs (SC-10) and tightened the
  import-linter contracts (PR-01 closed-box floor + core-light surface; PR-04
  CLI/web independence).

## [0.1.1] - 2026-06-16

### Changed
- Bumped the `mgf-common` pin to `>=0.47,<0.48` (was `<0.47`) — mgf-common 0.47
  is the pure-library release (the CLI moved to `mgf-fed[cli]`); mgf-cloud only
  uses the importable API, which is unchanged. This unblocks installing
  `mgf-fed[cli,cloud]` together (the `[cli]` extra requires mgf-common 0.47).
- Regenerated `.woodpecker.yml`: the supervisor lane calls `mtest` directly
  instead of the moved `mgf-common test` frontend.

## [0.1.0] - 2026-06-15 — maiden release

`mgf-cloud` is built over **7 passes** (see
`docs/inprogress/mgf-cloud-progress.md`). The provider-agnostic cloud-resource
interface for the federation — Infomaniak Public Cloud (OpenStack), AWS-ready —
with a CLI, a local web GUI, the SSH bootstrap layer, and `mgf-fed` integration,
**live-verified** end-to-end against the real Infomaniak project.

### Added
- **Pass 1 — conformant scaffold.** New federation sibling `mgf-cloud` under the
  `mgf.*` namespace: the provider-agnostic cloud-resource interface (Infomaniak
  Public Cloud first; AWS-ready). Ships:
  - the package skeleton (`mgf.cloud`, `py.typed`) + the `mgf-cloud` CLI entry
    point (`version` subcommand only — the resource surface lands in Pass 4);
  - the optional-extra layering — `[infomaniak]` (openstacksdk), `[s3]`/`[aws]`
    (boto3), `[remote]` (paramiko), `[web]` (fastapi/uvicorn/jinja2/mgf-fastapi),
    `[standards]` — keeping the core light;
  - the four gates green (ruff, mypy --strict, pytest, import-linter), the
    generated `.woodpecker.yml`, and the DOC-01 doc set incl.
    `docs/design/DESIGN.md` + the 7-pass progress tracker;
  - registry registration (`MGF_LIBRARIES`, 30 libraries) — `mgf-fed drift-check`
    reports clean.
- **Pass 2 — core abstraction.** The provider-agnostic surface:
  - `mgf.cloud.models` — 12 frozen pydantic resource models (`Server`, `Volume`,
    `FloatingIP`, `SecurityGroup`, `ObjectContainer`, …) + `ServerSpec`/`VolumeSpec`
    inputs + the `mgf:owner` tagging helpers (the stateless-identity mechanism);
  - `mgf.cloud.config` — `Credentials` (OpenStack app-credential or password, with
    `SecretStr` so secrets never log) + `clouds.yaml`/`OS_*`/`$MGF_CLOUD_CLOUDS_YAML`
    loaders + `CloudConfig`;
  - `mgf.cloud.providers` — the runtime-checkable `CloudProvider` Protocol, a full
    in-memory `FakeProvider`, and the lazy `get_provider` factory (no SDK at import);
  - `mgf.cloud.exceptions` — reuses mgf-common's vocabulary + `CredentialsError`/
    `ProviderNotFoundError` (EH-08 machine-readable fields);
  - a per-module **core-light** import-linter contract + a PUBLIC_API anti-drift pin
    test. 34 tests, 88.6% coverage; drift-check 23/23.
- **Pass 4 — CLI.** The `mgf-cloud` command-line interface (argparse) over the
  core: `provider`, `vm`, `flavor`/`image`, `volume`, `floating-ip`, `secgroup`,
  `keypair`, and `object` groups, with `--json` / `--dry-run` / `--yes` guardrails
  and secrets never printed. Tested end-to-end against the FakeProvider (47 tests,
  88.4% coverage). Built before Pass 3 so it's verifiable without the live
  Infomaniak credential.
- **Pass 5 — local web GUI.** `mgf-cloud web` launches a localhost (127.0.0.1,
  no auth) FastAPI console — `mgf.cloud.web.create_app(provider)` + `serve()` wired
  with the mgf-fastapi `RequestIdMiddleware` and an `AppError`→HTML error handler,
  plus 11 Jinja templates (dashboard + VM/volume/floating-ip/secgroup/keypair/object
  pages with create/delete/reboot/release forms). Drives the same `CloudProvider` as
  the CLI. `[web]` extra gained `python-multipart`; `[dev]` gained `httpx`. 55 tests,
  89% coverage; templates ship in the wheel; live `curl` smoke green.
- **SSH remote/bootstrap layer** (Pass 3's credential-free half, built ahead of the
  Infomaniak adapter). `mgf.cloud.remote` (`[remote]`, paramiko): `RemoteHost` /
  `RemoteResult` / `RemoteClient` Protocol + `FakeRemoteClient`, `connect()`
  (host-key reject-by-default, `accept_new` for a fresh host), `run_commands`,
  `install_docker`, `deploy_compose_bundle`; CLI `remote exec|push` and
  `bootstrap <host> --docker --compose-dir <dir>` — the mechanism Pass 6 uses to
  stand the self-hosted Woodpecker host up. 68 tests, 88% coverage.
- **Web GUI round-out.** Full object-storage management (container create/delete,
  container detail, object upload / download / delete), security-group + keypair
  create/delete, and volume attach/detach — all from the web console. 72 tests,
  88.8% coverage.
- **Pass 3 — Infomaniak adapter (the live cloud interface).**
  `mgf.cloud.providers.infomaniak.InfomaniakProvider` — the full `CloudProvider`
  Protocol on openstacksdk (`[infomaniak]`): compute (boot-from-volume with
  `delete_on_termination=False`, reboot/resize/snapshot, base64 user-data),
  keypairs, block storage, networking (floating IPs + security-group rules), and
  Swift object storage. The `_boundary` translates every `openstack.exceptions`
  **and** keystoneauth1 auth errors (401 → `CredentialsError`) into the federation
  vocabulary (EH-02); the `Connection` builds from `Credentials` (v3password or
  app-credential, auto-selected) and authenticates lazily, so unit tests inject a
  fake connection. `mgf.cloud.defaults` — the live-confirmed Infomaniak catalogue
  (auth_url, region `dc3-a`, image, flavour, networks, ingress rules, the
  org-hardening cloud-init). **Live-verified** against the real project: a nano VM
  created → reached ACTIVE → destroyed; Nova accepts the `mgf:owner` colon key.
- **Pass 6 — mgf-fed integration.** mgf-cloud is consumable as the `mgf-fed[cloud]`
  extra (lazily imported); `mgf-fed provision-ci-host` stands up the self-hosted
  Woodpecker CI host end-to-end via mgf-cloud (security group 22/80/443 →
  Docker-cloud-init VM dropping the `deploy/woodpecker/` bundle → public IP),
  **live-verified**. The live runs corrected the network model to the real cloud:
  `ext-net1` is **direct-public** (VMs boot with a routable public IP — no floating
  IP needed); the floating-IP pool is `ext-floating1` (not `ext-net1`); FIP attach
  is via Neutron association (the legacy Nova proxy 404s); `create_server` releases
  a floating IP if its attach fails (no leak).

[0.1.0]: https://codeberg.org/magogi-admin/mgf-cloud/releases/tag/v0.1.0
