# mgf-cloud — build progress tracker

> Living tracker for the 7-pass build of `mgf-cloud`. Design rationale:
> [`../design/DESIGN.md`](../design/DESIGN.md). Each pass is its own PR.
> Legend: ✅ done · 🔄 in progress · ⏳ pending.

## Summary

| Pass | Focus | Status |
|---|---|---|
| 1 | Scaffold + conformance + design docs | ✅ |
| 2 | Core abstraction (models, config, provider base) | ✅ |
| 3 | Infomaniak adapter (openstacksdk) | ✅ |
| 4 | CLI | ✅ |
| 5 | Local web GUI | ✅ |
| 6 | mgf-fed integration + resume WF-11 | 🔄 (command live-verified; WF-11 activation human-gated) |
| 7 | Review pass + release 0.1.0 | ✅ **RELEASED** (Codeberg, REST 200) |

---

## Pass 7 — Review pass + release 0.1.0 ✅ (2026-06-15)

**RELEASED: mgf-cloud 0.1.0 on Codeberg PyPI (REST 200 verified).** Review pass —
ruff/ruff-format/mypy --strict/lint-imports/pytest all green (94 passed, 89% cov),
wheel builds with `py.typed` + adapter/defaults; CHANGELOG finalized to `[0.1.0]`
with the Pass 3 + Pass 6 entries (PR #16). Released via `mgf-fed release-local`
(build→validate→smoke→local index) then `mgf-fed release-remote` (annotated tag
**v0.1.0** pushed + twine publish + REST 200 verify). Registry regenerated —
**REL-13 GREEN + CURRENT** (30 libraries; records the mgf-fed→mgf-cloud[infomaniak]
edge; mgf-standard PR #123).

> **Follow-up DONE (mgf-fed PR #26, 2026-06-15):** mgf-fed got its PAPER-102 scaffold
> (the canonical hook + `check_commit_trailers.py`, byte-exact from mgf-standard) and
> its `.woodpecker.yml` was regenerated → CI now installs `.[dev,supervisor,cloud]`,
> so `provision-ci-host`'s apply tests run in CI instead of skipping. drift-check clean.
>
> **The only thing left in the whole mgf-cloud arc is WF-11 activation** — purely
> human steps: a Codeberg OAuth2 app, a domain + DNS, and a Woodpecker API token. The
> infra half is one `mgf-fed provision-ci-host` away.

## Pass 1 — Scaffold + conformance + design docs ✅

**Focus:** a conformant, building, gated, registered skeleton + the planning/design docs.

- [x] Repo seeded from a current namespace sibling (`mgf-django`), django code/docs pruned
- [x] `pyproject.toml` — name/version/desc, core deps, extras (`[infomaniak]`/`[s3]`/`[aws]`/`[remote]`/`[web]`/`[standards]`/`[dev]`), `[tool.uv.index]` codeberg-mgf, ruff/mypy/pytest/coverage, `[tool.mgf.ci]`
- [x] `src/mgf/cloud/` skeleton (`__init__`, `__main__` CLI dispatch, `py.typed`) + `tests/unit/test_smoke.py`
- [x] `.importlinter` closed-box contract
- [x] venv synced (`uv sync --extra dev`) — mgf-common 0.46.11 etc. from Codeberg
- [x] four gates green: ruff (src/tests), mypy --strict, lint-imports, pytest + coverage 100%
- [x] registered in `MGF_LIBRARIES` (30 libraries) via `mgf-fed registry`
- [x] `mgf-fed scaffold` → generated `.woodpecker.yml`; `mgf-fed drift-check` → **23/23 clean**
- [x] `PUBLIC_API.json` generated (`tools/generate_public_api_json.py` → mgf.cloud)
- [x] keystone docs: README, SCOPE, DEPS, CHANGELOG, PUBLIC_API.md, **docs/design/DESIGN.md**, **this tracker**
- [x] supporting docs de-django'd (STARTHERE, CONTRIBUTING, SECURITY, FEEDBACK, LESSON_LEARNED, docs/memory, docs/reference/EVENTS, docs/standards, docs/testing, docs/inprogress/*, docs/cutover, docs/CLAUDE.md)
- [x] conformance ledger declared **L1**
- [x] wheel builds (`uv build` → `mgf/cloud` + `py.typed` + the `mgf-cloud` entry point)
- [x] initial commit `bb1e0ac` (AG-03 trailers) on `main`

**Verify:** `mgf-fed drift-check` clean (23/23) · 4 gates green · `mgf-cloud version` runs · wheel builds · no stray `django` references. **✅ all met.**

> **Pending federation-side commit:** `mgf-standard/MGF_LIBRARIES.{json,md}` was
> regenerated locally to register mgf-cloud (30 libraries) — left as an uncommitted
> working-tree change in mgf-standard; it gets committed (+ PR'd) at Pass 6/7 when
> mgf-cloud is wired into mgf-fed + released (REL-13 regens at release anyway).
> mgf-cloud has **no Codeberg remote yet** — Pass 1 is a local checkpoint.

## Pass 2 — Core abstraction ✅

**Focus:** the provider-agnostic API. **Landed:** `models.py` (12 frozen pydantic
resource models + `ServerSpec`/`VolumeSpec` + `mgf:owner` tagging helpers),
`config.py` (`Credentials` w/ `SecretStr` + `clouds.yaml`/`OS_*`/override loaders +
`CloudConfig`), `exceptions.py` (reuses mgf-common's vocabulary + `CredentialsError`/
`ProviderNotFoundError`, EH-08 fields), `providers/base.py` (the `CloudProvider`
runtime-checkable Protocol), `providers/fake.py` (full in-memory impl), and
`providers/__init__.py` (`get_provider` lazy `importlib` factory). `.importlinter`
gained the per-module **core-light** contract. PUBLIC_API anti-drift pin test added.
**Verified:** ruff · ruff-format · mypy --strict · lint-imports (2 contracts) ·
pytest **34 passed, 88.6% cov** · drift-check **23/23**.

## Pass 3 — Infomaniak adapter ✅

**Landed (2026-06-15, credential in hand — `my_stuff/infomaniak/FACTS.md`):**
`providers/infomaniak.py` — `InfomaniakProvider`, the full `CloudProvider` Protocol
on openstacksdk (`[infomaniak]`): compute (servers w/ boot-from-volume BDM +
`delete_on_termination=False`, floating-IP attach, reboot/resize/snapshot, base64
user-data), keypairs, block storage (create/attach/detach/snapshot), networking
(floating IPs, security groups + rules), and Swift object storage. Speaks only
`mgf.cloud.models` types across the boundary; **EH-02** `_boundary()` translates
every `openstack.exceptions` into the federation vocabulary
(`ResourceNotFoundError`/`ResourceAlreadyExistsError`/`ProviderError`). The
`Connection` builds from `Credentials` (v3password **or** v3applicationcredential,
auto-selected) and authenticates lazily, so unit tests inject a fake connection.
`defaults.py` — region/image/flavour/network/S3-endpoint/ingress-rules/org-hardening
cloud-init from DESIGN §6 (auth_url + region confirmed against the live `openrc`).

**Method/attr surface verified against the installed openstacksdk** (introspected
the real proxy signatures — `create_snapshot`, `add_floating_ip_to_server`,
`block_device_mapping_v2`, resource field names like `flavor.original_name` /
`is_bootable` / `floating_ip_address`) so the live path matches the SDK, not a
remembered API.

**Verified:** ruff · mypy --strict (17 files) · lint-imports (2 contracts KEPT —
core stays SDK-free, openstack confined to the adapter) · pytest **90 passed,
3 skipped** · coverage **89%** (adapter 89%, defaults 94%; the uncovered lines are
the live connection-build path). New tests: `tests/unit/test_infomaniak.py`
(19 tests, stateful fake `Connection` — lifecycle, attr-building, mapping, EH-02
translation) + `tests/integration/test_infomaniak_live.py` (opt-in, double-gated on
`MGF_CLOUD_LIVE=1` — live nano-VM create→ACTIVE→destroy with teardown in `finally`).

**SSH remote/bootstrap layer** (the credential-free half, built ahead) — `remote.py`
(`[remote]`, paramiko): `RemoteHost`/`RemoteResult`/`RemoteClient` Protocol +
`FakeRemoteClient`, `connect()` (host keys reject-by-default; `accept_new` for a
fresh host), `run_commands`, `install_docker`, `deploy_compose_bundle`; CLI
`remote exec|push` + `bootstrap <host> --docker --compose-dir <dir>` — exactly how
Pass 6 stands the Woodpecker host up.

**✅ LIVE-VERIFIED 2026-06-15** against the real `mgf-fed` project (dc3-a): the opt-in
integration suite ran **2 passed** — read-only catalogue + a nano-VM
create→ACTIVE→destroy round-trip. Confirmed live: auth (v3password), the SDK surface
end-to-end, Nova **accepts the `mgf:owner` colon metadata key** (resolves the
models.py §4 caveat), and the real catalogue names — which were stale in the
playbook-derived defaults and are now corrected in `defaults.py`:
`Ubuntu 24.04 LTS Noble Numbat`, flavour `a1-ram4-disk20-perf1` (the smallest live
shape — there is no 2 GB flavour), network `ext-net1`. Two hardening fixes the run
surfaced: keystoneauth1 401 → `CredentialsError` at the boundary (PR #12), and the
live test got a boot-appropriate `@timeout(600)` + leak-safe teardown (delete by
name + poll for async deletion). No billable resources leaked.

**✅ SSH remote/bootstrap layer DONE** (the credential-free half, built ahead): `remote.py`
(`[remote]`, paramiko) — `RemoteHost`/`RemoteResult`/`RemoteClient` Protocol + `FakeRemoteClient`,
`connect()` (host keys reject-by-default; `accept_new` for a fresh host), `run_commands`,
`install_docker`, `deploy_compose_bundle`; CLI `remote exec|push` + `bootstrap <host> --docker
--compose-dir <dir>` — exactly how Pass 6 stands the Woodpecker host up. 11 tests; the live-SSH
paths run in the Pass 6 smoke.

## Pass 4 — CLI ✅

**Focus:** the `mgf-cloud` CLI. **Landed:** `cli/_app.py` (argparse parser +
dispatch) + `cli/_output.py` (`--json`/compact `emit`). Groups: `provider`
(list/check), `vm` (list/show/create/delete/reboot/resize/snapshot), `flavor`/
`image` (list), `volume` (list/create/attach/detach/delete/snapshot),
`floating-ip` (list/create/attach/detach/release), `secgroup` (list/create/
delete), `keypair` (list/create/delete), `object` (containers/mkcontainer/
rmcontainer/ls/put/get/rm). Guardrails: `--dry-run` on every mutation, `--yes`
for destructive ops, `--json`, secrets never printed; common flags ride each leaf
via `parents=[common]`. (`remote`/`bootstrap` come with Pass 3; `web` with Pass 5.)
**Verified:** 4 gates green · pytest **47 passed, 88.4% cov** · drift-check **23/23**.

> **Note:** Passes 4 + 5 are built before Pass 3 (the Infomaniak adapter) so they
> verify against the FakeProvider while the Infomaniak credential is pending.

## Pass 5 — Local web GUI ✅

**Focus:** the local single-user web console. **Landed:** `web/_app.py`
(`create_app(provider)` FastAPI + `serve()`) wired with the mgf-fastapi
`RequestIdMiddleware` + an `AppError`→HTML error handler; 11 Jinja templates
(dashboard, VMs list/detail/new, volumes, floating-ips, secgroups, keypairs,
objects, error). Pages + form POSTs (create/delete/reboot/release) drive the same
`CloudProvider` as the CLI. `mgf-cloud web [--host 127.0.0.1] [--port 8787]`
launches it (lazy `[web]` import; localhost-only, no auth). Templates ship in the
wheel. `[web]` extra gained `python-multipart`; `[dev]` gained `httpx`.
**Verified:** 4 gates green · pytest **55 passed, 89% cov** (web module 98%) ·
drift-check **23/23** · live `curl` smoke (dashboard + pages 200).

## Pass 6 — mgf-fed integration + resume WF-11 🔄

**Built + LIVE-VERIFIED (2026-06-15, mgf-fed PRs #24/#25 + mgf-cloud #14):**
- **`mgf-fed[cloud]` extra** = `mgf-cloud[infomaniak]`, lazily imported (core mgf-fed
  stays zero-dep; graceful degradation when absent).
- **`mgf-fed provision-ci-host`** (`src/mgf/fed/_provision.py`) — plans + applies the
  CI host end-to-end via mgf-cloud: security group (22/80/443) → VM whose cloud-init
  installs Docker, opens ufw, and drops `deploy/woodpecker/` into `/opt/woodpecker/`
  (secrets kept OUT of user-data). `--dry-run` plans; `--yes` applies; prints the
  operator/human next-steps. 13 tests (pure plan/cloud-init + apply via FakeProvider).
- **`deploy/woodpecker/`** bundle (server+agent+Caddy auto-TLS, Forgejo/Codeberg) —
  already committed; `.env` gitignored.
- **Live-verified apply** against the real project: `provision-ci-host --yes` created
  the secgroup + a Docker-cloud-init VM (ACTIVE, **public IP** `195.15.x.x`) tagged
  `mgf:owner=mgf-fed`, then full teardown — no billable leak.

**Live findings folded in:** ext-net1 is **direct-public** (VMs boot with a routable
public IPv4+IPv6 — no floating IP needed → provision defaults to no-FIP); the FIP pool
is `ext-floating1` not `ext-net1`; FIP attach is via Neutron, not the legacy Nova
proxy (mgf-cloud #14). The live smoke also caught + fixed a real FIP leak.

**⛔ Remaining = HUMAN-GATED (WF-11 activation):** stand up the *persistent* host +
turn on CI. Needs (a) a Codeberg **OAuth2 app** (client/secret), (b) a **domain + DNS**
A/AAAA at the host (Caddy TLS), (c) a **Woodpecker API token** (browser OAuth) to
enable repos + branch-protection. See `wf11-activation-blocked-on-token`. Once the
host runs: enable `mgf-*` repos, pilot-verify, fleet. **Not regenerated:** mgf-fed's
`.woodpecker.yml` (the generator would install the unpublished mgf-cloud) — regen at
Pass 7 after mgf-cloud's release, which also turns on the provision apply tests in CI.

## Pass 7 — Review pass + release 0.1.0 ⏳

**Focus:** conformance (drift-check 23/23, 4 gates, DOC-01, bump conformance L2/L3);
security review (creds, localhost web, TLS, adversarial adapter review — idempotency,
partial-failure cleanup, leaks, retry); **live end-to-end smoke** (create nano
VM+volume+floating-IP, reachable, full teardown, no leaks); optional `CL-*` cloud
rules family in mgf-standard; release mgf-cloud 0.1.0 → Codeberg + bump mgf-fed;
regen registry (REL-13); REST 200 verify.
