Profile table keyed on the contract's axis codes, per-provider reference sections for kubernetes, gcp and kvm, shared gaps and proposed features. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
124 lines
6.1 KiB
Markdown
124 lines
6.1 KiB
Markdown
# 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`
|
|
- [x] 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_<type>.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.
|
|
|
|
## Step 3 — Write `docs/providers-existing.md`
|
|
|
|
2026-09-16 11:35. The profile table has exactly one row per contract axis
|
|
code, checked by the same loop as Step 2 with a row count. Everything else
|
|
comes from the old blueprint's §6, §7 and §9 plus a fresh read of the three
|
|
packages, with every `file:line` replaced by a symbol name.
|
|
|
|
Corrections to earlier notes applied while writing: kvm's Checker `Details`
|
|
also carry `user` and `pool`; the kubernetes provider has no seam interface
|
|
(`newWithClient` injects a `client.Client`, the fake client stands in); kvm's
|
|
`classify` has an `isClassified` pass-through. Timeouts were checked per
|
|
provider for the `timeouts` row: kubernetes sets no `rest.Config.Timeout`,
|
|
gcp relies on SDK defaults, kvm bounds only the SSH handshake
|
|
(`defaultHandshakeTimeout` in `dialSSH`), so all three are weaker than the
|
|
contract's SHOULD and the table says so.
|
|
|
|
Symbol check on the appendix (every backticked identifier grepped for in
|
|
Go, Makefile and YAML sources):
|
|
|
|
```sh
|
|
grep -ohE '`[A-Za-z_][A-Za-z0-9_.]*`' docs/providers-existing.md | tr -d '`' | sort -u | while read s; do
|
|
grep -rqwF --include='*.go' --include='Makefile' --include='*.yaml' -- "$s" . || echo "$s"
|
|
done
|
|
```
|
|
|
|
The residue is file names, dotted `Type.method` forms, the `timeouts` axis
|
|
code, libvirt's `nostate`, and the API names `OwnerReference` and
|
|
`rest.Config.Timeout`; nothing unexpected.
|
|
|
|
Worth noting: matrix rows that the profile table now carries (Create
|
|
non-blocking, GC marks, IP source, quota detection, Checker presence,
|
|
credentials, logging, tracing, tests) were dropped from "Other differences"
|
|
rather than kept twice.
|