# Changelog — mgf-cloud-provision

Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); SemVer once it ships its first release.

## [0.1.2] - 2026-07-15

### Fixed (packaged cloud-init)
- The agent cloud-init shipped in the wheel (`data/woodpecker/woodpecker-agent-cloud-init.yml`)
  now carries the self-heal fixes: age-guarded reapers (no more racing live pipelines) + the
  disk-pressure image reclaim + the fail-closed reconciler learnings. A NEW agent provisioned
  from 0.1.1 would have installed the old, pipeline-killing self-heal. The running estate was
  already fixed by direct deploy; this closes the gap for future provisioning.

## [0.1.1] - 2026-07-14

### Changed
- **Released what was stranded on `main`.** `main` had moved past `v0.1.0` while still
  declaring `0.1.0`, so the *published* `0.1.0` was different code under the same version
  number. Two things consumers never received:
  - the **Apache-2.0 relicence** (landed on main 2026-07-12; every published wheel still
    carried `License: MIT`, with no `NOTICE` — a relicence is not real until it is released);
  - the **widened `mgf-common` pin** (`<1.0`, was `<0.50`). The stale published cap
    backtracked the whole federation's co-install to `mgf-common` 0.49.2.

  `REL-13` could not see this: it compares declared version to *registry* version, and both
  said `0.1.0`. The drift was main-vs-tag content. `mgf/tools/verify_release_currency.py`
  now catches the class — *a fix that isn't released isn't shipped.*

## [Unreleased]

### 2026-07-10 — queue sweep: the CI safety net can no longer die silently (PAPER-01/02)

**Changed**
- **`ops/agent-reconciler.py` mints its Woodpecker JWT per-run from the DB**
  (PAPER-01): `mint_user_jwt()` HS256-signs the user JWT with the DB `hash` as
  key (stdlib; byte-identical to `mgf` `tools/woodpecker_api.sh`), read via
  `docker exec … psql`. The static `/opt/woodpecker/reconciler.token` — which
  401'd silently for 17 days after a hash rotation — is demoted to a DB-down
  fallback; each run logs which token path it used.
- **The self-heal layer heartbeats on success** (PAPER-02): `agent-reconciler.py`
  (`job=agent-reconciler`) and `self-heal.sh` (`job=self-heal`, incl. the agent2
  cloud-init mirror) POST a success-only record to the OO `backup_heartbeats`
  stream via the existing `/etc/mgf/backup-heartbeat.{url,token}` files —
  fail-open, same dead-man's-switch contract as `backups/*`.

**Added**
- **`ops/estate-watch-heartbeats.sh`** — heartbeat freshness check for the agent2
  estate watcher (stale > 30 min for self-heal jobs, > 26 h for backups → page;
  exit 2 when the check itself can't run). Deployment/wiring steps in the header.
- Unit tests for the mint + heartbeat logic and the shell wiring pins
  (`tests/unit/mgf/cloud_provision/test_ops_reconciler.py`); runbook §4/§6.3/§6.4
  updated. Live deployment of the updated scripts is the supervisor's step.

### 2026-07-04 — Wave-2 P0 (survival): the provisioner becomes truthful

**Added**
- **`ci-host` verb** (`mgf.cloud_provision.ci_host` + `mgf-cloud-provision ci-host`) — the
  Woodpecker CI-host provisioner absorbed from the now-deprecated `mgf-fed
  provision-ci-host` (Court P0.4: one provisioner). Renders the **current** production
  bundle into cloud-init — the Postgres compose (`examples/woodpecker-compose.yml`) and
  the two-vhost Caddyfile (`examples/woodpecker-Caddyfile`, new: web/API + `grpc.<domain>`
  h2c) — never the pre-migration SQLite topology the old verb reproduced. `--with-agent`
  converges `woodpecker-agent2` from the packaged agent spec in the same run. Packaged
  bundle files are **byte-pinned to `examples/`** by the unit tests (the drift that made
  the old verb render a stale topology is now structurally impossible). Applies through
  the repo's own converge engine (CREATE/NOOP/REPLACE + `--allow-replace`), plus the CI
  security group (22/80/443) ensured first.

- **Backups as code** (`backups/` — Court P0.1, deployed by hand today, codified here):
  `devpi-export-nightly.sh` (stop → export → start → prune 7 d → optional Swift),
  `pg-dump-nightly.sh` (compose `pg_dump | gzip` → prune 14 d),
  `oo-snapshot-nightly.sh` (volume tar → prune 7 d) — each ends by POSTing a
  **heartbeat** (dead-man's-switch: posted only on success; URL/token are root-600
  file *paths* under `/etc/mgf/`, values never committed). Per-host cloud-init
  fragments (`examples/*-backup-cloud-init-fragment.yml`) install script + cron.d
  entry and are **byte-pinned** to the scripts by unit tests.
- **`docs/backup-restore-runbook.md`** — RPO **24 h** / RTO **4 h** stated; per-store
  restore procedures (devpi import + password-reconcile, Postgres restore + org-secret
  re-entry, OO volume restore); the watcher/pager pattern; the restore-drill record
  table (P0 acceptance).
- **Self-heal co-location** (`ops/` — canonical home): `ops/agent-reconciler.py`
  (moved from `examples/`) + `ops/self-heal.sh` (from mgf-fed, now parameterized via
  `WOODPECKER_STACK_DIR` so one script serves both hosts). The mgf-fed copies carry
  DEPRECATED headers on its `wave2/p0-survival` branch.
- **devpi password-reconcile** (`examples/devpi-cloud-init.yml` →
  `/usr/local/bin/devpi-password-reconcile.sh`) — the documented post-rebuild step: set
  the `magogi` publish password FROM the operator-provided value (the Woodpecker org
  secret `devpi_password`), never the random first-boot mint that silently broke CI
  publishes after a rebuild. The cloud-init header documents the landmine + the step;
  the restore runbook carries it in the devpi procedure.

**Fixed**
- **PKG-06 dep-confusion exposure** — the bare `[tool.uv] index-url` became a named
  `[[tool.uv.index]] "magogi-prod"` with `explicit = true` + `[tool.uv.sources]` pinning
  every `mgf-*` dependency to it: an `mgf-*` name can never be shadowed by a public
  package nor silently fall back to PyPI (this repo's own ledger flag; ledger row
  updated 🚫→⏳ — the endpoint stays devpi pending the PKG devpi EXTEND, founder gate P4).
- `PCU-HOESFJV-clouds.yaml` → `PCP-HOESFJV-clouds.yaml` — the propagated clouds-yaml
  filename typo in `examples/devpi.toml`, `examples/woodpecker-agent.toml`,
  `examples/openobserve.toml` and the CI runbook (the project is `PCP-HOESFJV`).

### 2026-06-18 — Phase 1a skeleton

Initial skeleton of the federation's declarative provisioning library (self-hosted-infra rollout, Phase 1a).

**Added**
- `ServiceSpec` / `ServicePlan` / `ApplyResult` / `Action` — frozen pydantic models for desired state + plan/apply outcomes.
- `plan()` / `apply()` / `destroy()` / `to_server_spec()` — the converge engine over any `mgf.cloud.providers.base.CloudProvider`. `plan` is read-only (CREATE / NOOP / REPLACE); `apply` gates destructive REPLACE behind `allow_replace`.
- `load_spec()` — TOML spec loader (with `cloud_init_file` inlining) → `ServiceSpec`; typed `SpecError` on bad input.
- `devpi_service_spec()` + `examples/devpi.toml` — the first customer, encoding the Phase-0-resolved sizing (`a2-ram4-disk50-perf1` / Ubuntu 24.04 / `ext-net1`).
- `mgf-cloud-provision` CLI — `plan` / `apply` / `destroy`, with `--provider fake` for cloud-free runs.
- `ProvisionError` / `SpecError` extending `mgf.common.exceptions`.
- Full unit coverage against `mgf-cloud`'s in-memory `FakeProvider` (no cloud, no credentials).

**Not yet**
- Live `infomaniak` provider end-to-end + the devpi cloud-init converge (Phase 1b).
- Federation registration (`MGF_LIBRARIES` + roster + `mgf-fed scaffold` for generated CI + policy files + conformance tracker).
