Twenty-four fill-in entries, one per decision the contract leaves to the provider, numbered to match the contract's profile table. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
15 KiB
Provider spec template
Copy this file to docs/<type>-provider-spec.md, fill in every Answer
line, and hand the result to whoever implements the provider together with
provider-contract.md and
providers-existing.md. An unanswered entry blocks
implementation: it is a decision the contract leaves to the provider, and the
implementer must not guess it.
Where an entry names an axis in backticks, the allowed answers are the profiles of that axis in contract §4, and the entry number matches the last column of the contract's §4.20 table. Evidence is where the answer was confirmed: a link into the backend's API documentation, or the experiment that was run. "Verify against the target version" is a valid interim answer only for the Evidence line, never for the Answer.
Header
- Type (the
type:value in the providers config, lowercase): - Example configured name:
- Backend and API version this spec was written against:
- Client (
vendor-sdkwith the module path, orthin-client): - Author and date:
1. Identity and providerID
Question. What is the providerID's shape, which location parts does it embed, and what validates the config values pasted into it?
Allowed answers. <location>/<name>, <location>/<allocated-key>, or a
location-free <allocated-key> (contract §6; required when instances can
migrate, see entry 2).
Obligations. Strict parsing; ErrPermanent for a malformed ID from Get
and Delete; a regexp on every config value that becomes an ID segment, in
config_<type>.go.
Answer:
Evidence:
2. Primary key (primary-key)
Question. Is req.Name the backend's key, or does the backend allocate an
identifier?
Allowed answers. name-is-key; allocated-id.
Obligations. allocated-id: Create finds an existing instance by name
before allocating; allocation is safe against a concurrent Create for
another Proxy (deterministic from req.UID, or allocate-then-verify); Get
and Delete use the allocated key; state how a collision with an instance
the operator does not own is detected and reported.
Answer:
Evidence:
3. Placement mapping
Question. What do Placement.Region, Zone, MachineType and Image
mean for this backend, which are required, which are ignored, and what
happens when a value is unknown?
Allowed answers. Per field: a config catalogue key, a backend identifier passed through, or ignored (contract §2.1, MAY ignore with documentation).
Obligations. Unknown values are rejected with ErrPermanent naming the
field and value before any backend call; ignored fields are listed in the
provider's guide because they are still in the spec hash; an empty value with
a single-choice catalogue may default, and the guide says so.
Answer:
Evidence:
4. Image source
Question. Where does the boot image come from, and what does a catalogue edit do to running instances?
Allowed answers. A catalogue in the config block (key to URL or backend
image ID); a backend template or image referenced directly by
Placement.Image; a URL fetched by the provider into the backend on first
use.
Obligations. If the provider fetches or converts images: streaming, no buffering (contract §1.4), a completeness marker so a cut-off fetch is redone, concurrency control so two Creates fetch once, and a written warning that replacing an image in use may corrupt instances that depend on it. If the backend owns the image: the prerequisite is documented in the guide.
Answer:
Evidence:
5. Cloud-init delivery
Question. How does CreateRequest.CloudInit reach the guest, byte for
byte?
Allowed answers. User-data metadata on the instance; a NoCloud seed built in-process and attached as media; a file placed on the backend and referenced from the instance config; not delivered (documented, with how the proxy gets configured instead).
Obligations. Size limits of the channel stated; whether vendor-data or a second document can ride along; the sentinel test (contract §11); the channel must be fully reachable through the API the provider uses (no step that needs shell access on the backend unless the spec says the provider has it).
Answer:
Evidence:
6. Machine type mapping
Question. How does Placement.MachineType become CPU, memory and disk?
Allowed answers. A catalogue of named profiles in the config block; a backend size name passed through.
Obligations. A catalogue needs validation of each profile; disk size default and its config field named.
Answer:
Evidence:
7. Network and IP discovery (readiness, ip-source)
Question. Which network does the instance attach to, where does its
address come from, how long until it is usable, and what makes it reachable
from the operator pod and from clients on spec.port?
Allowed answers. readiness: immediate; provisioning-window.
ip-source: assigned-at-create; discovered (name the mechanism: guest
agent, DHCP lease, neighbour table, and the order if several).
Obligations. discovered: what the guest must run, how it gets
installed, what happens without it; discovery failure is Provisioning, not an
error; only routable IPv4 is reported (state the filter). Reachability:
firewall or routing steps the provider performs, or the prerequisite the
guide states. Whether Port is used by the provider at all.
Answer:
Evidence:
8. GC marks and CreatedAt (gc-mark, created-at)
Question. Where do LabelManaged and LabelUID live, where does
CreatedAt come from, and which secondary resources are marked?
Allowed answers. gc-mark: native-labels; written-metadata;
mark-window. created-at: backend-timestamp; written-at-create.
Obligations. mark-window: the window's length in seconds, why it is
far below MinAge, and how the same Proxy's next reconcile cleans an
unmarked instance. Any value-format constraint of the backend's labels
(allowed characters, length) and how a Proxy UID fits it.
Answer:
Evidence:
9. State mapping (provisioning-state)
Question. How does every backend state map to StateProvisioning,
StateRunning, StateStopped, StateTerminated, and how does Get tell
"defined but never started" from "stopped"?
Allowed answers. A table of backend state (plus lock or task flags) to
provider state, with the unknown-state row mapping to StateStopped.
provisioning-state: native; create-blocks; marker.
Obligations. Running requires an IP; marker names what is recorded at
creation and how Get reads it; create-blocks names the cap on the wait.
Answer:
| Backend state | Lock or task | Provider state |
|---|---|---|
Evidence:
10. Create: blocking and task tracking (create-return, async-ops)
Question. What does Create wait for before returning, and how are
asynchronous backend tasks tracked?
Allowed answers. create-return: submit; blocking. async-ops:
none; task-handle; backend-lock.
Obligations. task-handle: where the handle is kept and how a failed
task surfaces (in Get, as which class, with which message). backend-lock:
what Get and Delete return while locked. Every step of Create is
idempotent so a cut-off attempt resumes; list the steps and what each finds
on a retry.
Answer:
Evidence:
11. Delete and secondary resources (delete-return, secondary-resources)
Question. In what order does Delete tear things down, what does it do
when called again mid-delete, and what does Get return meanwhile?
Allowed answers. delete-return: returns-early; waits.
secondary-resources: none; cascade (name the flags set at create);
explicit (name each resource and how it is found by name).
Obligations. Idempotent at every step; "already deleting" is success and cheap; a location no longer in the config is a logged no-op success; any resource that can outlive the instance without being listable is a documented GC blind spot.
Answer:
Evidence:
12. ListByTag scope and partial failure (list-partial)
Question. What is the full scope ListByTag enumerates, and what happens
when part of it is unreachable?
Allowed answers. fail-whole; omit-unreachable.
Obligations. The scope covers everything the provider can create in; server-side filtering on the mark where the backend allows it; a single unreadable instance may be skipped with a log line; the cost of one list at fleet size.
Answer:
Evidence:
13. Error classification (not-found, quota)
Question. Which backend responses map to which of the four classes, and which signal means "does not exist"?
Allowed answers. A table of backend status or code (with message pattern
where needed) to class. not-found: status-404; error-code;
list-and-miss. quota: detected; absent.
Obligations. One classify, a table test with captured real responses
for every matched message; the permission-denied-versus-quota case decided;
authentication failures, host-key or certificate failures, and cancellation
each assigned a class with a reason.
Answer:
| Backend response | Class | Why |
|---|---|---|
Evidence:
14. Credentials and TLS (credentials)
Question. What credential does the operator use, where does it come from, when is it read, and how is the backend's TLS certificate trusted?
Allowed answers. mounted-files (Secret name, mount path, file names,
optional: true); environment; workload-identity. TLS: system-roots;
ca-file; insecure (the flag's exact name).
Obligations. The minimum privilege set the credential needs, listed; rotation behaviour (free, or restart needed); the sentinel test for the credential (contract §7); the constructor never dials.
Answer:
Evidence:
15. Config block
Question. What are the fields of <Type>Config, their defaults, and their
validation?
Allowed answers. A table: field, required, default, validation, and which identifier or path the value ends up in.
Obligations. Contract §10: setBlocks, the type allow-list in
Config.validate and the constructor map in cmd/main.go, config_<type>.go
with its tests, regexps for values pasted into IDs or file names.
Answer:
| Field | Required | Default | Validation | Ends up in |
|---|---|---|---|---|
Evidence:
16. Checker
Question. What are the check targets, what does one check do, which
reasons can it report, and what goes into Details?
Allowed answers. One target per thing that fails independently; the
shared reasons from internal/provider/check.go first, provider-specific
reasons only with a stated "what the admin does differently"; Details keys
listed.
Obligations. Same credential, verb and scope as ListByTag and Create;
cheap (one page, one object); never disrupts a reconcile that shares a
connection; returns promptly on ctx; the status.type comment in
api/v1alpha1/providerhealth_types.go updated.
Answer:
Evidence:
17. Client and child spans (client-kind, child-spans)
Question. What talks to the backend, and how do its calls appear under the operation span?
Allowed answers. client-kind: vendor-sdk (module path, and whether it
is maintained); thin-client; rpc-over-ssh. child-spans:
sdk-instrumentation; transport-wrapper; span-decorator; none.
Obligations. The seam interface's name and the fake that implements it;
thin-client: one error type carrying status and body, transport timeouts,
parent-gated otelhttp; none is a documented gap; the span recorder test
(contract §11).
Answer:
Evidence:
18. Timeouts (timeouts)
Question. What bounds each backend call, the dial, and any long step such
as a download or a wait loop, and what does a cancelled ctx do to a shared
connection?
Allowed answers. per-call (the budgets, per operation); ctx-only
(cite the client's own timeout).
Obligations. Contract §1.5: no caller sets a deadline; a hung call pins a worker or stalls GC. Cancellation behaviour on a shared connection stated (abandon the call, or close the connection).
Answer:
Evidence:
19. Concurrency
Question. What is shared between concurrent calls (connections, caches, locks), and what happens when two calls for the same instance name overlap?
Allowed answers. A description of the shared state and its locking.
Obligations. Safe for 3 reconciles, a GC sweep and a check round at once;
two Delete calls for one instance overlap safely; one fetch per image per
location when several Creates race.
Answer:
Evidence:
20. Logging (logging)
Question. Which tiers does the provider log, with which keys on every line?
Allowed answers. tiered; outcome-only; none (cite the client's own
logging).
Obligations. Logger from logf.FromContext(ctx) on every call; never
CloudInit or credentials; provider and the instance name on every line
that concerns an instance.
Answer:
Evidence:
21. Tests (backend-test)
Question. What is the fake, which contract tests exist, and how is the provider proven against the real backend?
Allowed answers. backend-test: cluster-e2e; integration-tag (the
<TYPE>_TEST_* variables, the make test-integration-<type> target); manual-runbook.
Obligations. Every item of the contract §11 list, ticked here:
- repeat
Create GetafterDeleteDeletetwice- malformed ID
- unknown state maps to
StateStopped StateRunningrequires an IPUIDandCreatedAtread backListByTagexcludes unmarked instancesGetduring an in-progress deleteGetduring an asynchronous operation or lock- never started is never
StateStopped - cancelled
ctxreturns promptly classifytable with captured responses- Checker reasons, expired
ctx, stable order - sentinel tests for
CloudInitand the credential - child spans under the operation span
- real-backend lifecycle test
Answer:
Evidence:
22. Deployment
Question. What changes in config/ and the Makefile?
Allowed answers. The list: the optional: true mount in
config/manager/manager.yaml; the commented entries in
config/manager/providers_config.yaml and
config/samples/providers-config.yaml; config/samples/proxy_<type>.yaml
with a cloud-init that installs a proxy on the sample image; a Makefile
helper for the Secret; RBAC markers if the provider talks to the cluster's
own API; whether hack/providers-dev.yaml gets an entry.
Answer:
Evidence:
23. Docs
Question. Which documents are written or updated?
Allowed answers. docs/<type>-provider.md with its section list; the row
and section in providers-existing.md; the architecture.md rows (components
table, §5, §11, and §10 if spans are added); the README row; the
docs/testing.md section; the CHANGELOG.md entry after confirmation.
Answer:
Evidence:
24. Known limits accepted at design time
Question. Which of the contract's §13 limits bite this provider in practice, and which new gaps does this design accept?
Allowed answers. A list, each with the consequence for an operator and the workaround.
Answer:
Evidence: