# Sync Schema Descriptions

> Sources Terraform schema descriptions from the pinned oxide.go SDK's Go doc comments, which already mirror the OpenAPI spec. Adapts the text to the TF surface (translating reshaped attribute names, clarifying NameOrId inputs, trimming API-internal paragraphs, and reconciling any divergence between provider and API default behavior) so the descriptions stay accurate and aligned with the version of the SDK the provider actually calls into.

- Skill: `oxidecomputer/sync-schema-descriptions` (Agent Skill)
- Install (CLI): `npx skillmds@latest add oxidecomputer/sync-schema-descriptions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/oxidecomputer/sync-schema-descriptions/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: oxidecomputer (https://skillmd.com/u/oxidecomputer)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/oxidecomputer/sync-schema-descriptions

---


# Sync Schema Descriptions

## Overview

Terraform schema `Description` / `MarkdownDescription` strings are hand-written
and drift from the canonical descriptions in the OpenAPI spec. The oxide.go
SDK, pinned via `go.mod`, has the same descriptions baked into Go doc comments
on each field. This skill walks through sourcing the right description,
adapting it, and applying the change.

Not every TF attribute maps 1:1 to a single SDK field (e.g. TF's `boot_disk_id`
corresponds to the SDK's richer `boot_disk`), so blind copying is wrong. The
skill codifies the adaptation process rather than automating it blindly.

## Workflow

### 1. Resolve the SDK source

    go list -m -f '{{.Dir}}' github.com/oxidecomputer/oxide.go

Descriptions live in `<dir>/oxide/types.go`. Do not use a local checkout of
the SDK, which might not match the pinned version.

### 2. Identify scope

The user will usually ask about one attribute (e.g. `instance.ssh_public_keys`),
a whole resource, or all resources. For each in-scope attribute, work through
steps 3–8.

### 3. Locate the TF attribute

In `internal/provider/<resource>/resource.go` (or `data_source.go`), find the
schema block and note the current `Description` / `MarkdownDescription`. Find
the TF model struct field via its `tfsdk:"<attr_name>"` tag.

Do not touch schema-version-compat files (`resource_v0.go`, `resource_v1.go`,
…). They represent frozen prior schemas kept for state upgrades; descriptions
there are not rendered to current docs, so the files should stay as they
were when that version shipped.

### 4. Find the matching SDK field

**Input attributes (set in Create/Update):** grep the Create method for the
model field. You'll see a line like `params.Body.SshPublicKeys = ...`. The SDK
struct is the type of `params.Body` (e.g. `oxide.InstanceCreate`); the SDK
field is the LHS accessor.

**Computed/output attributes (populated in Read):** grep the Read method for
how `state.<Field>` is set from the API response — e.g.
`state.TimeCreated = types.StringValue(resp.TimeCreated.String())`. The SDK
struct is the response type (e.g. `oxide.Instance`), the field is the `resp.<X>`
accessor.

If the TF attribute has no clean 1:1 SDK counterpart (flattened, split, or
composed from multiple API fields), stop and surface this to the user — it
needs a hand-written description, not an adaptation.

### 5. Read the SDK doc comment

Open `<sdk_dir>/oxide/types.go`, find `type <StructName> struct`, and read the
Go doc comment on the field. Comments are hard-wrapped; reflow them into a
single logical description.

Also note the field's Go type — it drives adaptation rule (b) below.

### 6. Adapt

**The default is verbatim.** Copy the SDK doc comment's prose into the TF
description word-for-word. Do not insert em dashes, merge sentences,
paraphrase verbs, or restructure for flow. The value of sourcing from the SDK
is fidelity to a single maintained source of truth; editorial drift
reintroduces exactly the hand-written divergence the skill is meant to
eliminate.

Only deviate when one of the rules below applies, and only to the extent the
rule requires. If you aren't sure whether a change is required, keep the
source wording and ask the operator.

**Do not invent text from sibling attributes.** If the SDK has no usable
description for a given TF attribute (e.g. the SDK doc comment is a
tautology like "X is a variant of Y", or the corresponding SDK type/field is
internal wrapping with no prose), the skill's job is done — the attribute
needs a hand-written description, which is outside this skill's scope.
Surface the gap to the operator. Never copy text from a TF sibling
(`v4` → `v6`, `ephemeral` → `floating`, etc.) and pass it off as adapted; it
is still hand-written, just plagiarized.

**a. Translate field-name references.** If the comment references an API field
name that differs in TF (e.g. "set the `boot_disk` attribute"), rewrite it to
the TF name (`boot_disk_id`). Same for any other cross-referenced field.

**b. `NameOrId` phrasing.** The API accepts both names and IDs for
`NameOrId` / `[]NameOrId` inputs. The right phrasing depends on what the TF
attribute represents:

- If the attribute is the resource's own `name` (typically a required
  top-level attribute on a resource or data source), it stores the name.
  Describe as "Name of …".
- If the attribute is a reference to another resource (e.g. `vpc_id`,
  `disk_attachments`, `boot_disk_id`), the provider writes the resolved ID
  back into state on Read, so a user who supplies a name will see a
  perpetual diff (or recreate) on the next plan. Until the provider
  preserves the user-supplied form, keep descriptions honest: say "ID of
  …" / "IDs of …" regardless of whether the SDK field type is `string` or
  `NameOrId`. If the source SDK comment says "name or ID", rewrite to "ID".

**c. Trim non-user-facing paragraphs.** Drop:
- internal API behavior the TF user can't observe
- roadmap/hedging language ("currently, …", "in the future, …")
- information already encoded by the TF schema (e.g. defaults — TF renders
  those separately)

Keep paragraphs describing user-visible behavior: accepted value forms,
interactions with other attributes. Paragraphs about omission/defaults need
verification first — see rule (e).

**d. Preserve TF-specific additions.** If the current TF description contains
information that isn't in the SDK (e.g. cross-refs to other TF attrs, validator
behavior), keep it — it usually reflects provider logic that doesn't exist at
the API level.

**e. Provider defaults may diverge from API defaults.** Any SDK phrase like
"if not provided, …", "if null, …", or "defaults to …" describes what the
_API_ does when the field is absent from the request — but the provider may
not actually omit the field. Helpers like `shared.NewNameOrIdList` return
`[]T{}` (never nil), and whether that serializes as `[]` or gets dropped
depends on the SDK struct's JSON tag (`omitempty` / `omitzero` vs. bare). An
empty list reaching the API is semantically different from an absent field.

If the SDK description includes a default-behavior clause, decide how to
handle it:

- **Inspect the code.** Trace the provider's Create/Update path for the field
  and confirm whether a null TF value produces an omitted request field or a
  zero-value one. If the provider's behavior matches the SDK clause, keep it;
  if it doesn't, rewrite to describe what the provider actually does.
- **Ask the operator.** If the behavior isn't clear from the code, or if the
  mismatch looks like a provider bug worth fixing separately, surface it.
- **Omit the clause.** When in doubt, drop the default-behavior sentence
  entirely rather than risk a misleading description.

**f. Add undocumented TF-specific constraints.** Rule (d) is about keeping
what's already in the current description; this rule is about adding what's
missing. If the schema has validators or plan modifiers whose effect isn't
mentioned in the current description, fold a short clause in — the
framework's auto-generated docs don't surface these. Examples:

- `stringvalidator.LengthBetween(1, 63)` → "Must be 1–63 characters."
- `stringvalidator.RegexMatches(pattern, "…")` → reuse the validator's
  message, or describe the pattern.
- `stringvalidator.OneOf("a", "b", "c")` → "Must be one of `a`, `b`, `c`."

Skip constraints the framework already renders (`Required` / `Optional` /
`Computed` flags, `Default` values — same reasoning as rule (c)).

### 7. Apply, then hand off for review

Apply all proposed changes with `Edit` to the current `resource.go` /
`data_source.go` only (never `resource_v*.go`). Run `make fmt` to regenerate
`docs/resources/*.md` and `docs/data-sources/*.md`, then `make lint` to verify.

Report what was changed in a short summary and tell the operator to review via
`git diff`. The diff is a better review surface for multi-attribute text
changes than an in-chat bulleted list.

### 8. Show the provenance table

At the end of the pass, emit a table with one row per touched attribute:

| Attribute | SDK source (field, verbatim) | Provider description (as applied) |

For each row:
- **SDK source**: the type + field (e.g. `InstanceCreate.Memory`) and the
  doc-comment prose as it appears in `oxide/types.go`, unedited. For rule (f)
  additions, use "(none — rule f)" and name the validator.
- **Provider description**: the final string now in the schema, so the
  operator can see the adaptation deltas inline without chasing through
  `git diff` and the SDK side-by-side.

One row per attribute updated — skip attributes left untouched.

Exceptions — pause and surface to the operator **before** editing rather than
after:
- Step 4 found no clean 1:1 SDK counterpart (reshaped, split, or composed
  attribute) — the description needs to be hand-written, not adapted.
- Rule (e) flagged a behavior-claim mismatch that implies a provider fix is
  needed rather than a description change. Describe the mismatch and ask
  whether to narrow the description to match current provider behavior or open
  a separate issue to fix the provider first.

## Notes

- The SDK pins to a specific Omicron release; the descriptions on disk match
  the API the provider actually calls at runtime. There is no need to fetch
  the OpenAPI spec directly.
- When `go.mod` bumps `oxide.go`, upstream descriptions may change. Re-running
  this skill after an SDK bump is a reasonable drift-detection pass.

