A pass over the API types, spec hash, health and discovery call sites, manager manifests, samples, README, provider docs, plans and the PR review found rules the blueprint did not state: the IP must be reachable on the proxy port by the health engine and clients, providers must be safe for concurrent use, the manager's read-only/128Mi runtime envelope, catalogue edits bypass the spec hash, Delete is re-called while in progress, and provider names must be DNS-1123. Adds matrix rows, a deployment checklist item, and the still-open review findings and broken samples as gaps. README's "Adding a provider" restated an outdated contract; it now points to docs/providers.md, and the providers table gains the kvm row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.4 KiB
Execution: provider blueprint
Plan: docs/plans/2026-09-16-0841-provider-blueprint.md
- Step 0 — Save the plan; wait for the merge
- Step 1 — Collect evidence (read-only, in parallel)
- Step 2 — Write
docs/providers.md - Step 3 — Verify, then open the MR
Step 0 — Save the plan; wait for the merge
The plan was committed on feat/provider-health (80b2e9c) and reached main
through PR #6. After the user confirmed the merge, the branch was cut from the
updated main:
git switch main && git pull --ff-only
git switch -c feat/provider-blueprint
Worth noting: .claude/settings.json has local, uncommitted permission edits
that carried over through the switch. They are deliberately left out of this
branch.
Step 1 — Collect evidence (read-only, in parallel)
Three Explore agents answered the plan's 12-item questionnaire, one each for
kubernetes, gcp and kvm. Meanwhile I read the shared layer:
internal/provider/*.go, registry.go, the provider wiring in cmd/main.go,
reconcileManaged/replaceInstance/reconcileDelete in the controller,
internal/gc/gc.go, and how the monitor uses Checker.
Before relying on the agents' claims, I spot-checked them against the code. One
was wrong and did not go into the doc: the kvm agent said Delete skips volume
removal when DomainDestroy returns ErrNoDomain. In fact removeDomain
returns nil there, and Delete still calls removeVolumes afterwards
(kvm.go:477-482).
Rules that exist only in the callers, and not in any comment, were found by reading the controller and GC. They went into the doc as MUST rules:
- A zero
CreatedAtmakes an instance immediately collectable (gc.go:120). GetreturningErrPermanenton the deletion path makes the reconciler callDeleteanyway.- Replacement waits for
Getto return NotFound before creating an instance with the same name. StoppedandTerminatedboth lead to delete-and-recreate.
Worth noting, found outside this plan's scope and not changed here:
docs/gcp-vm-validation.mdfilters onlabels.proxy-operator-managed=yes, but the value istrue.- According to the gcp agent,
docs/gcp-in-specific-project.mdholds a real project ID, IAM output and an egress IP. The user was told.
Step 2 — Write docs/providers.md
Wrote the eight sections the plan lists. Linked the new doc from the Provider
contract row in docs/architecture.md and from the CLAUDE.md "Architecture"
section.
Judgment calls:
- The file:line evidence sits in per-provider notes under the matrix, as planned. The contract rules in §2 cite the reconciler and GC lines each rule protects.
- The gap list includes documentation drift (kvm-provider.md's ARP order and log keys, gcp-vm-validation.md's label value and its claim that async errors reach status), as well as code gaps. Both would mislead the next provider author in the same way.
- "Proposed features" stays a flat list with no design, as the user decided.
Worth noting: the line numbers in the doc are a snapshot. The doc says so and asks readers to look for the named function when a line has moved.
Step 3 — Verify, then open the MR
An Explore agent re-checked the doc against the code: about 150 claims, including every matrix cell. It found four mismatches, and all four were fixed:
- Startup dial: I had listed kvm-provider.md's "connection kept open from
startup" as doc drift. It isn't: the pool dials lazily, but the provider
health monitor runs a check as soon as the leader starts
(
monitor.go:147, default interval 1m), and that check opens the connection. The bullet was removed and the kvm notes now say this. - Checker wording: §5 said the monitor type-asserts
Checker. It iscmd/main.go:216; the monitor only sees nil or not. - Reasons, not conditions: §3 called
QuotaExceededandPermanentErrorconditions. They are reasons onProvisioned. - Latch lines: the line range is
:133-139, not:130-135.
It also noted that none of the providers assert var _ provider.Provider, and
that the kubernetes provider's RBAC markers live in the controller package. The
§7 checklist now says both.
Link check (every relative ](…) target and anchor in the new and edited docs
resolves), then the sanity build:
go build ./... && go test -short ./...
# all packages ok
Worth noting: no CHANGELOG entry yet. Per CLAUDE.md it goes in after the user confirms the doc.
Follow-up — tracing was missing from the blueprint
The user pointed out, after the PR was opened, that the blueprint said almost nothing about tracing. The plan's questionnaire item 10 had collected the evidence, but the doc reduced it to a matrix row about logging and a checklist line telling authors not to open spans. That line even suggests tracing needs no work. A provider author needs the opposite message: the operation span comes free, and child spans for backend calls are theirs to add.
Added a new §6, "Observability: tracing, logs and metrics", and renumbered the matrix, checklist and gaps sections to §7–§9. It covers:
- Three layers: decorator operation spans; child spans per backend call, with parent-gated HTTP transports or a span decorator over the SDK seam; and logs.
- The ctx and logger rules that keep
traceIDon log lines. - A per-provider status table:
- kubernetes: parent-gated
RestConfigWrapperspans. - gcp: the Google SDK's own
otelhttp, never disabled by the provider. - kvm: no spans.
trace.gois a log decorator.
- kubernetes: parent-gated
Also added two tracing rows to the §7 matrix, rewrote checklist item 8 as
"Tracing and logging" with an in-memory span-recorder test, and added gaps (kvm
has no child spans; only kubernetes is checked against Tempo, and only at
operation level). A proposal notes that the kvm libvirtAPI seam takes no
ctx, so per-RPC spans need that plumbed in.
The links were re-checked, including the new architecture.md#10-tracing-internaltracing anchor.
Follow-up — a completeness sweep
The user asked for one more pass over everything provider-related. After the tracing miss, this pass read beyond the provider packages:
- the Proxy API types and the spec hash;
- the health engine and discovery call sites that consume
Instance.IP; - the manager Deployment, the providers ConfigMap and the samples;
- README, testing.md, kvm-provider.md and the GCP docs;
- the initial prompt, the proxy-operator and kvm plans, and the 2026-08-10 PR review;
- the CHANGELOG.
What the blueprint was missing, now added:
- What the instance is for. The IP and port must be reachable from the operator's health probes and from discovery clients. Providers don't know which proxy runs; the proxy comes from cloud-init, except for kubernetes.
- Concurrency. Three reconciles, GC and checks can call a provider at once, so it MUST be safe for concurrent use.
- The runtime envelope: read-only root filesystem, a 64Mi
/tmp, a 128Mi memory limit, credentials as optional mounts. - Create:
placementcan't be validated by the CRD, so error messages must name the field. Catalogue edits in config aren't in the spec hash. - Delete is re-called every
DeletionPollwhile a delete is in progress. - Provider names must be DNS-1123 for
ProviderHealth, andspec.provideris immutable. - Metric names in §6.
- Three matrix rows: proxy software, IP reachability, distinct egress.
- A deployment checklist item: credentials mount, config entries, sample Proxy, Makefile helper.
- Gaps:
- still open from the PR review: GC has no installation identity; an
unconfigured provider latches Proxies; kubernetes Pod RBAC always installed
and no
OwnerReferenceon Pods; - the duplicated type list;
- samples that can't work:
proxy_gcp.yamluses a debian-cloud image without cloud-init, andproxy_kvm.yamlplaces onnas1where configs definebeelink; - the
"mock"example in thespec.providerdoc.
- still open from the PR review: GC has no installation identity; an
unconfigured provider latches Proxies; kubernetes Pod RBAC always installed
and no
Outside the plan's "leave other docs alone" line, README.md changed:
- Its "Adding a provider" section restated the contract, with the old "Create
never blocks" rule that kvm contradicts. It now points to
docs/providers.mdinstead of keeping a second copy that disagrees. - The "Implemented providers" table gained the missing kvm row.
- The deploy note no longer claims the default ConfigMap ships only the kubernetes provider.
The links were re-checked for providers.md and README.md.