docs: proxmox provider spec

Phase 1 of the proxmox provider plan: docs/proxmox-provider-spec.md
answers all 24 entries of the provider spec template against the
contract, citing the Phase 0 facts from pv1-1 as evidence.

It folds in the ten corrections recon found. The two that matter most:
Create inspects a taken VMID, waiting out a clone lock, before probing
past it, so a crash-retry adopts its own half-cloned VM instead of cloning
a duplicate; and Get checks the lock before the name, because a VM being
cloned has no name yet.

The plan now opens with a note that the spec wins where they disagree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 09:18:29 +00:00
co-authored by Claude Opus 5.5
parent ff94fe70bb
commit 4e974f30f6
3 changed files with 915 additions and 1 deletions
@@ -5,7 +5,7 @@ Pairs with [docs/plans/2026-09-18-1502-proxmox-provider.md](../plans/2026-09-18-
## Status
- [x] Phase 0 — Plan file, branch, host recon, fixtures
- [ ] Phase 1 — The spec (`docs/proxmox-provider-spec.md`)
- [x] Phase 1 — The spec (`docs/proxmox-provider-spec.md`)
- [ ] Phase 2 — Config block, registration, package skeleton
- [ ] Phase 3 — Extract `nocloud` and `keyedlock` from kvm2
- [ ] Phase 4 — Seam, HTTP client, decorator, fake
@@ -280,3 +280,52 @@ volumes of 199999. The capture is `agent_network_get_interfaces_vlan.json`.
- Time to IP was about 50 s on both networks, with the agent installed at
first boot. Budgets in the plan (running wait 60 s for the VM state, the
agent discovered by `Get` polling) are consistent with this.
## Phase 1 — The spec
Wrote [docs/proxmox-provider-spec.md](../proxmox-provider-spec.md), all 24
entries of the template, from the plan, the contract and the Phase 0 facts.
The ten Phase 0 corrections are folded in, and the plan now opens with a
note that the spec wins where the two disagree.
Judgment calls made here, each recorded in its entry:
- **Create's allocation loop** (entry 2) runs an ownership check on any
taken VMID: wait out a lock or a nameless entry, then adopt when the name
and UID are ours, skip when the name differs, refuse as Permanent when the
name is ours but the mark is not. This is the fix for the duplicate-VM bug.
- **Get's check order** (entry 9): lock, then name and mark from the config,
then `status/current`, then the agent. `lock: suspended` alone maps to
Stopped, since a hibernated VM never resumes by itself.
- **Agent messages** (entry 7): `QEMU guest agent is not running` is
Provisioning, `VM <vmid> is not running` is Transient, and
`No QEMU guest agent configured` is **Stopped**, so the reconciler replaces
a VM that can never report an address instead of latching the Proxy.
- **Templates are checked before cloning** (entry 4): not a template, or a
Proxmox cloud-init drive in any slot, is Permanent. This makes the "no
cloud-init drive" prerequisite enforceable instead of a silent failure.
- **`isoStorage` is required**, with no default (entry 15), because the token
must hold `Datastore.Allocate` on it.
- **TLS verification failures are Permanent**, with a Checker reason
`CertificateInvalid` (entries 13, 16), since the fix is the URL or CA file.
- **The CA is loaded lazily and cached**; the token is read per request
(entry 14).
Checks, as the plan's Verify clause asks:
```bash
grep -c '^## [0-9]*\. ' docs/proxmox-provider-spec.md # 24
grep -n '^\*\*Answer:\*\*$' docs/proxmox-provider-spec.md # nothing
grep -c '—' docs/proxmox-provider-spec.md # 0
```
plus a script comparing every `` `<axis>`: `<profile>` `` pair against the
contract's §4.20 table (no mismatches, every axis answered) and a list of
the F numbers cited (all 23).
Worth noting: the first pass stated two things as fact that recon could not
show and was corrected before commit. That an unreachable node's guests stay
listed as `unknown` is Proxmox's documented design, unverifiable on a single
node; entry 13 now says so. And the matcher for `VM <vmid> is not running`
is a whole-message regexp, because ` is not running` alone also matches
`QEMU guest agent is not running`.