# Changelog — mgf-vm-core

All notable changes land here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.14] - 2026-07-14

### Changed
- **Released what was stranded on `main`.** `main` had moved past `v0.1.13` while still
  declaring `0.1.13`, so the *published* `0.1.13` 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.13`. 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.*

## [0.1.13] - 2026-06-26

### Changed
- Release the **mgf-common 0.49** pin (`<0.50`) onto devpi so the full
  fleet co-installs at the 0.49 cornerstone (devpi backfill; no API change).

## [0.1.12] - 2026-06-16

### Changed
- Widened the `mgf-common` pin to `<0.48` — mgf-common 0.47 is the pure-library release (the end-user CLI moved to `mgf-fed[cli]`); the importable API is unchanged.

## [Unreleased]

## [0.1.10] — 2026-06-06 — federation cohort: mgf-common 0.46 walk

Walk the `mgf-common` pin ceiling `<0.46 → <0.47` to accept the
library-API-neutral `mgf-common 0.46.0` (CLI-only release: new
`federation`/`design` commands + a `doctor --standards` gate; no library
surface change).

---

## [0.1.9] — 2026-06-05 — creator validator: loosen live-state Protocol (D-G creator)

### Changed
- **`creator.validator._NameNetworkProvider.list_networks`** now returns
  `Iterable[_Named]` (a `.name`-only Protocol) instead of `Iterable[NetworkInfo]`
  — the validator only reads network names, so a client adapter yielding
  engine-neutral `NetworkProfile` objects satisfies it unmapped. Mirrors the
  umbrella's D-G23 loosening; lets the app repoint its create-wizard validation
  onto the pure-core validator (toward deleting the umbrella `mgf.vm.creator`).

## [0.1.8] — 2026-06-05 — HypervisorBackend.rename (D-G26)

### Added
- **`HypervisorBackend.rename(d, new_name)`** — a REQUIRED method (rename is
  universal): renames an inactive domain in place, preserving its `DomainId`
  identity. Raises `DomainStateInvalid` (running), `DomainAlreadyExists` (name
  taken), `DomainNotFound` (absent); same-name is a no-op. + `OperationKind.VM_RENAME`
  (cardinality 34 → 35, additive D-D6), `FakeHypervisorBackend.rename`, and
  conformance scenario `CONF-H-12`. Lets the app's `vm rename` drain off `VMProvider`.

## [0.1.7] — 2026-06-05 — DomainPolicy operation kinds (D-G25)

### Added
- **`OperationKind.VM_SET_AUTOSTART` + `VM_MANAGED_SAVE`** — the two mutating
  operations the SDK routes to the `DomainPolicyBackend` (0.1.6). Additive per
  D-D6 (forward-compatible); cardinality 32 → 34.

## [0.1.6] — 2026-06-05 — DomainPolicyBackend optional Protocol (D-G25)

### Added
- **`DomainPolicyBackend`** — the 6th OPTIONAL backend Protocol: `get_autostart`
  / `set_autostart` / `managed_save`. The neutral surface for the two
  umbrella-only domain ops the app's `vm` CLI still needs (host-boot autostart
  policy + managed-save suspend-to-disk), so they can drain off `VMProvider`.
  Modeled on `MetricsBackend` (D-G13) / `GuestAgentBackend` (D-G19); ratifies
  moving autostart onto a neutral contract (it was provider-only before).
- **`FakeDomainPolicyBackend`** — in-memory fake (per-domain autostart flag +
  managed-save running-precondition), exported from `mgf.vm.core.fakes`.

## [0.1.5] — 2026-06-05 — snapshot-info enrichment (D-G21)

### Added
- `_SnapshotInfoContract` gains `state: VmState | None` — the VM run-state
  captured in a domain snapshot (`None` for a volume snapshot or when unknown).
  `FakeHypervisorBackend.snapshot` captures the domain's current state.

## [0.1.4] — 2026-06-04 — storage-read enrichment (D-G20)

### Added
- `PoolInfo` gains `available_bytes` and `active`; `_VolumeInfoContract` gains
  `path` and `allocation_bytes` — so consumers can render full pool/volume usage
  off the neutral contracts.
- `StorageBackend.refresh_pool(name) -> PoolInfo` — a read-path verb that
  rescans a pool's backing store and returns its fresh info. `FakeStorageBackend`
  implements it (observably `get_pool`) and populates the new fields.

## [0.1.3] — 2026-06-04 — federation cohort: mgf-common 0.45 walk

PATCH: pin widened to `mgf-common>=0.41,<0.46` to accept mgf-common 0.45
(Codeberg cutover — PKG-01..12). `mgf-*` resolve from the `codeberg-mgf`
index (PKG-06). No API change.

## [0.1.2] - 2026-06-04

### Added
- **OPTIONAL `GuestAgentBackend` Protocol (D-G19, Wave R6).** A new injectable
  backend mirroring `MetricsBackend`/D-G13 for in-guest agent observation:
  `guest_ping(d) -> bool`, `guest_os_info(d) -> GuestOsInfo`,
  `guest_interfaces(d) -> tuple[GuestNic, ...]`. Adds the frozen value types
  `GuestNic` and `GuestOsInfo` to `contracts`, a first-class
  `FakeGuestAgentBackend` (with `_register_domain` / `_set_reachable` /
  `_set_os_info` / `_set_interfaces` seed helpers), and the guest-agent
  conformance suite (`guest_agent_conformance_suite`, scenario CONF-G-01).
  Backward-compatible and additive — existing `>=0.1,<0.2` pins still resolve.

## [0.1.1] - 2026-06-03

### Changed
- Relaxed the `mgf-common` dependency ceiling to `>=0.41,<0.45` so the
  package accepts the `0.44.x` line of the cornerstone.
- Distribution moved to the Codeberg PyPI registry per
  `PACKAGE_DISTRIBUTION.md` (PKG-01/PKG-06): `mgf-*` dependencies resolve
  from the explicit `codeberg-mgf` index; this is the first release
  published to and verified against Codeberg.

## [Unreleased — historical: pre-0.1.1 development]

### Added
- **Phase C — Fakes, state machine, conformance suite (2026-05-25, v0.4.0).**
  Phase C of Phase 0 Stream A. The contract surface from v0.3.0
  becomes executable: working in-memory adapters that satisfy the
  Protocols, the lifecycle state machine the adapters drive, and
  the cross-adapter conformance suite that proves both.
  - `mgf.vm.core.fakes` — **`FakeHypervisorBackend`**,
    **`FakeStorageBackend`**, **`FakeDeviceBackend`** (D-F3
    first-class). ~1100 lines of real in-memory implementations
    with idempotency, typed errors, cancellation honoring, and
    observable state. Used by the SDK's `InProcessTransport`, by
    `make dev`, and by every downstream test suite.
  - `mgf.vm.core.state_machine` — **`VmLifecycleStateMachine`**.
    Pure Python; legal-transitions table; fires
    `LifecycleObserver` + `AsyncLifecycleObserver` per
    `contracts.md` §6. Observer exceptions caught and logged
    (never propagated). Slow observers (>50 ms) logged as
    warnings. `force_state()` bypasses the table for the
    reconciler's out-of-band updates. **`InvalidTransition`**
    raised on illegal transitions.
  - `mgf.vm.core.conformance` — the cross-adapter test catalog
    runner per `contracts.md` §10.
    **`hypervisor_conformance_suite()`**,
    **`storage_conformance_suite()`**,
    **`device_conformance_suite()`**.
    Initial scenarios:
    - CONF-H-01 (define→start→stop→undefine round-trip)
    - CONF-H-02 (define idempotency)
    - CONF-H-03 (start idempotency)
    - CONF-S-01 (create-list-delete volume)
    - CONF-S-03 (volume-with-snapshots refuses delete)
    - CONF-D-01 (claim→assignment_for→release round-trip)
    - CONF-D-04 (fenced-lease assignment raises)
    Capability-gated: scenarios skip themselves when the adapter
    reports unsupported features. The fakes pass the whole suite
    (eating our own dog food); real adapters land incrementally.
- 76 new tests: 48 for fakes, 22 for the state machine, 6 for the
  conformance runner. **219 tests total.**
- `__version__` bumped 0.3.0 → 0.4.0.

### Added
- **The four Protocols (2026-05-24, v0.3.0).** Phase B of Phase 0
  Stream A: real Python materialization of
  `vm-vmanager-docs/component-specs/contracts.md`:
  - `mgf.vm.core.contracts` — `HypervisorBackend` (~21 methods),
    `StorageBackend` (~15 methods), `DeviceBackend` (~11 methods),
    `LifecycleObserver` + `AsyncLifecycleObserver`. All are
    `runtime_checkable` `Protocol`s; long-running methods declare
    `token: CancellationToken | None = None` per DP-09.
  - `mgf.vm.core.ids` — typed ID wrappers (`VmId`, `HostId`,
    `DomainId`, `VolumeId`, `SnapshotId`, `DeviceId`, `LeaseId`,
    `OperationId`, `TenantId`) — frozen, slotted, distinct types
    even when wrapping the same string.
  - `mgf.vm.core.cancellation` — the `CancellationToken` Protocol
    + a default `CancellationEvent` impl backed by
    `threading.Event` (used by tests and fakes).
  - `mgf.vm.core.capabilities` — `HypervisorCapabilities`,
    `StorageCapabilities`, `DeviceCapabilities` for the
    `capabilities()` methods. The scheduler reads these for
    placement; the conformance suite reads them to skip
    capability-gated tests.
  - Supporting types in `contracts.py`: `VmSpec` (engine-neutral),
    `VolumeSpec`, `PoolSpec`, `DeviceSpec`, `DeviceInfo`,
    `DeviceAssignment`, `LeaseInfo`, `CompatibilityResult`,
    `DomainInfo`, `PoolInfo`, `MigrationTarget`, `MigrationPlan`,
    `VmState` (the lifecycle enum — distinct from the legacy
    `VMState` live-state dataclass), `TransitionCause`,
    `TransitionEvent`, `VmEvent`, `EventGap`, `LeaseEvent`,
    `ClaimMode`.
- **Exception hierarchy expanded per contracts.md §7.** Added new
  subtrees:
  - `HypervisorError` — `HypervisorConnectionError`,
    `HypervisorTransientError`, `DomainStateInvalid`, `SpecInvalid`,
    `SpecConflict`, `ResourceUnavailable`, `GuestUnresponsive`,
    `DeviceAttachFailed`, `DeviceDetachFailed`,
    `MigrationIncompatible`, `MigrationFailed`, `MigrationCancelled`,
    `SnapshotExists`, `SnapshotNotFound`, `SnapshotInUse`,
    `SnapshotHasChildren`.
  - `StorageError` — `StorageTransientError`, `PoolNotFound`,
    `PoolExists`, `PoolNotEmpty`, `VolumeNotFound`, `VolumeExists`,
    `VolumeInUse`, `VolumeHasSnapshots`, `InsufficientCapacity`,
    `ResizeUnsupported`, `FormatUnsupported`.
  - `DeviceError` — `BrokerConnectionError`, `BrokerTransientError`,
    `DeviceNotFound`, `DeviceContended`, `DeviceNotPermitted`,
    `HardwareFault`, `LeaseNotFound`, `LeaseExpired`,
    `DestUnreachable`.
  - `TransportError` — `TransportConnectionError`,
    `TransportTimeout`, `TransportCancelled`,
    `TransportProtocolError`.
  - `AuthError` — `AuthenticationFailed`, `AuthorizationDenied`,
    `CapabilityExpired`.
  - `RegistryError` — `ImageNotFound`, `ImageChecksumMismatch`,
    `ImageInUse`.
  - `SchedulerError` extensions: `NoHostAvailable`,
    `ConstraintUnsatisfiable`, `PolicyViolation`.
  - `OperationCancelled` (raised by `CancellationToken.raise_if_cancelled`).
  - Aliases: `VmManagerError = vm-vmanager-appError` (contracts-canonical
    single-cap),  `DomainNotFound = DomainNotFoundError`.
  - **Every exception carries `RETRYABLE: bool`** per §7.4 — the
    SDK + reconciler use this to decide retry-with-backoff vs.
    needs-human without string parsing.
- 135 new tests across `test_contracts.py` (Protocols' method
  names, signatures, `token` parameter on long-running methods,
  ID typing, enum membership, CancellationEvent semantics) and
  `test_exceptions.py` (full hierarchy via parameterized
  `issubclass`, subtree shape, retryability tags, chained
  exceptions per EH-03). **143 tests total, all passing.**
- N818 (PEP 8 exception-name suffix) added to ruff `ignore`:
  contracts.md uses short names (`DomainNotFound`, `VolumeInUse`)
  deliberately; the contract surface is the source of truth.
- `__version__` bumped 0.2.0 → 0.3.0.

### Added
- **Domain types ported from mgf-vm umbrella (2026-05-24, v0.2.0).**
  Phase A of Phase 0 Stream A: lifted the foundational types from
  `mgf-vm/src/mgf/vm/` into `mgf.vm.core.*`:
  - `mgf.vm.core.domain` — `VMHandle`, `VMState`, `VMStatus`,
    `VMSpec` (via attribute access), `VMMetrics`, `HostMetrics`,
    `VMCredential`, `IsolationLevel`, `NetworkInfo`, `LeaseInfo`,
    `StoragePoolInfo`, `VolumeInfo`, `SnapshotInfo`.
  - `mgf.vm.core.exceptions` — the full `vm-vmanager-appError` hierarchy
    (DomainNotFoundError, OperationError, SnapshotError, MigrationError,
    ImportExportError, …) including the `EnvironmentError` shadow alias.
  - `mgf.vm.core.common` — `atomic_io`, `observer`, `progress`,
    `safe_tar`, `shell`, `ssh`, `xml`, `yaml_migrations`. Internal
    references to `mgf.vm.{domain,common,exceptions}` rewritten to
    `mgf.vm.core.{domain,common,exceptions}`.
  - `__version__` bumped 0.1.0 → 0.2.0 to reflect the real content.
- Real smoke + integration tests: `tests/unit/mgf/vm/core/test_smoke.py`
  exercises the AP-01 `__all__` shape, the exception hierarchy
  inheritance, the dataclass-ness of `VMHandle`/`VMState`, the
  `VMStatus` enum, and a round-trip through `atomic_write`. **8 tests
  passing.**
- Mypy override for `defusedxml`, `defusedxml.*`, `opentelemetry`,
  `opentelemetry.*` (no py.typed upstream; OTel is optional).
- `types-paramiko` added to dev deps.

### Changed
- **Ruff config aligned with the original `mgf-vm`'s ruleset** so the
  ported code is clean today. Adds `A`, `C4`, `PIE`, `SIM`, `RET` to
  `select`; ignores `E501`, `B008`, `SIM108`; tests get `S101`,
  `N802`, `PLR2004` exceptions. The federation-stricter rules
  (`S` — bandit; `DTZ` — naive datetime; `ASYNC` — asyncio
  correctness) are deferred to a separate hardening sweep tracked
  in `vm-vmanager-docs/ROADMAP.md` Phase 0 Stream A.

### Status
All four gates green on Python 3.12:
- pytest: 8 passed
- `mypy --strict`: 12 files clean
- ruff: All checks passed
- lint-imports: 0 contracts (intentional; layering contracts land
  alongside the new Protocol surface)

### Pending (next Phase 0 Stream A iteration)
- `contracts.py` — the four Protocols
  (`HypervisorBackend`, `StorageBackend`, `DeviceBackend`,
  `LifecycleObserver`) per
  `vm-vmanager-docs/component-specs/contracts.md`.
- `fakes/` — first-class `Fake*Backend` implementations
  (D-F3).
- `state_machine.py` — `VmLifecycleStateMachine`.
- `conformance/` — the cross-adapter suite (CONF-H-*, CONF-S-*,
  CONF-D-*, CONF-X-*).
- New-name aliases (`VmHandle`, `VmSpec`, `VmId`, etc.) per the
  contracts spec, alongside the legacy `VMHandle`/`VMSpec` names.
- The federation-strictness sweep on the lifted code (re-enable
  `S`, `DTZ`, `ASYNC` with the necessary `# noqa` annotations).

### Federation context
- `mgf-vm` umbrella still ships `mgf/vm/{domain,exceptions,common,...}`
  for now. The deletion-from-umbrella + import-flip in consumers is
  the next phase of the extraction (tracked separately, expensive
  enough to want its own session).
- `mgf-vm-forensics` and `mgf-vm-interchange` still depend on the
  umbrella; their `[tool.uv.sources]` flips to `mgf-vm-core` once
  this content stabilizes.

### Earlier (skeleton)
- Initial repo skeleton (2026-05-24): `pyproject.toml` + four-gate
  scaffolding (ruff / mypy / pytest / import-linter), MIT license,
  README pointing to `vm-vmanager-docs` as the single source of truth.
- Federation scaffolding: `USERS.md`, `STANDARDS_NOTE.md`,
  `docs/inprogress/MGF_STANDARDS_CONFORMANCE.md`.
- `docs/claude/CLAUDE.md` via `mgf-common init-project --kind library`.

### Pending (Phase 0 — see `vm-vmanager-docs/ROADMAP.md`)
- `domain.py` — `VmSpec`, `VmStatus`, `VmState`, `IsolationLevel`, ID types.
- `state_machine.py` — `VmLifecycleStateMachine`.
- `contracts.py` — the four Protocols per `vm-vmanager-docs/component-specs/contracts.md`.
- `exceptions.py` — `VmManagerError` hierarchy.
- `fakes/` — `FakeHypervisorBackend`, `FakeStorageBackend`, `FakeDeviceBackend`.
- `conformance/` — the cross-adapter conformance suite (CONF-H-*, CONF-S-*, CONF-D-*, CONF-X-*).
