# 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` - [ ] 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.