Files
egress-proxies-operator/docs/plans-executions/2026-09-16-1108-provider-docs-split.md
T

143 lines
7.0 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`
- [x] 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.
## Step 4 — Rewrite `docs/providers.md` as the index; link edits
2026-09-16 11:40. `docs/providers.md` was rewritten in place (same path, so
the inbound links from `README.md`, `CLAUDE.md`, `docs/architecture.md` and
the historical plan files still resolve, and the pre-split blueprint stays
reachable in the path's git history). The three inbound sentences were
updated to name the new files:
- `README.md` "### Adding a provider": the first sentence now names the
contract, the template and the appendix, with `docs/providers.md` as the
index; the "Start there" sentence is unchanged.
- `CLAUDE.md` "## Architecture": the providers.md sentence now names all
three files and the index.
- `docs/architecture.md` components row "Provider contract": "rules in
provider-contract.md, per-provider profile table in providers-existing.md".
The historical files under `docs/plans/` and `docs/plans-executions/` keep
their `providers.md` links on purpose.