# Execution: Split the provider blueprint into contract, appendix, template and index Plan: [docs/plans/2026-09-16-1108-provider-docs-split.md](../plans/2026-09-16-1108-provider-docs-split.md) - [x] Step 0 — Save and commit the plan - [x] Step 1 — Write `docs/provider-contract.md` - [x] Step 2 — Write `docs/provider-spec-template.md` - [ ] Step 3 — Write `docs/providers-existing.md` - [ ] Step 4 — Rewrite `docs/providers.md` as the index; link edits - [ ] Step 5 — Verification sweep - [ ] Step 6 — Final summary, CHANGELOG after confirmation ## Step 0 — Save and commit the plan Committed as `cf89ac4` on the existing `feat/provider-blueprint` branch, on its own, before any document work. The pre-existing local modification of `.claude/settings.json` was left out of the commit. ## Step 1 — Write `docs/provider-contract.md` 2026-09-16 11:17. Written from scratch rather than edited down from the old blueprint, so that nothing provider-specific could survive by accident. Every MUST names its caller by function (`reconcileManaged`, `replaceInstance`, `reconcileDelete`, `providerFailure`, `deletionFailure`, `Sweeper.sweep`, `Monitor.checkOnce`, `Monitor.run`, `registry.Build`) and states what breaks. **Axis codes frozen** (19, §4.1 to §4.19): `readiness`, `create-return`, `delete-return`, `ip-source`, `gc-mark`, `created-at`, `list-partial`, `quota`, `not-found`, `primary-key`, `provisioning-state`, `async-ops`, `secondary-resources`, `credentials`, `client-kind`, `timeouts`, `logging`, `child-spans`, `backend-test`. The appendix table and the template are keyed on these strings; the §4.20 summary table's last column gives the template entry number each axis maps to. **Two rules that were not in the old blueprint**, both found by reading the callers while verifying: - `Sweeper.sweep` loops providers sequentially in one goroutine, and no caller puts a deadline on the context (only `Monitor.checkOnce` uses `WithTimeout`). So a hung `ListByTag` stalls orphan GC for every provider. Recorded in §1.5 and the `timeouts` axis. - The Stopped branch of `reconcileManaged` calls `Delete` and requeues without clearing `ProviderID`, then deletes again on every poll until NotFound. A backend that reports a freshly created, never started instance as stopped would have it deleted right after creation. Recorded as a MUST in §2.2 and as the `provisioning-state` axis with three allowed profiles. Checks run on the draft (all clean): ```sh grep -niE "kubernetes|gcp|kvm|libvirt|squid|google|compute engine" docs/provider-contract.md grep -n "—" docs/provider-contract.md grep -nE '\.go:[0-9]+|:[0-9]+-[0-9]+' docs/provider-contract.md grep -ohE "(internal|cmd|config|docs|api|hack|test)/[A-Za-z0-9_./-]+" docs/provider-contract.md | sort -u | while read p; do test -e "$p" || echo "MISSING $p"; done ``` The path check reports three expected misses: `config/samples/proxy_.yaml` is a pattern, `docs/provider-spec-template.md` is written in Step 2, and `internal/provider/providertest` is a proposal named in §13. Worth noting: the contract refers to "the operator pod" and "the cluster's own API" rather than naming the platform, so that the abstractness grep stays meaningful. The `hack/providers-dev.yaml` rule is phrased as "only if the type needs no credentials" because that file exists for `make run-dev` without any Secret. ## Step 2 — Write `docs/provider-spec-template.md` 2026-09-16 11:28. Twenty-four entries in a fixed shape (Question, Allowed answers, Obligations, Answer, Evidence). Thirteen of them carry an axis code and their numbers are the ones the contract's §4.20 table points at; the other eleven (identity, placement, image, cloud-init, machine type, config block, Checker, concurrency, deployment, docs, accepted limits) are decisions the contract leaves open without being a profile axis. Entries 9, 13 and 15 carry an empty table to fill in (state mapping, error classification, config fields); entry 21 carries the contract §11 test list as checkboxes so a filled spec doubles as the test plan. Check that every axis code appears in the template: ```sh for c in $(grep -oE '^### 4\.[0-9]+ `[a-z-]+`' docs/provider-contract.md | grep -oE '`[a-z-]+`' | tr -d '`'); do grep -q "\`$c\`" docs/provider-spec-template.md || echo "template missing $c" done ``` Clean. Worth noting: the preamble allows "verify against the target version" only on an Evidence line, never as an Answer, because the whole point of the file is that the implementer does not guess.