Skip to content

feat: generate actions, and verify every kind's bindings against the SDK - #38

Merged
ShocOne merged 2 commits into
mainfrom
feat/actions
Jul 30, 2026
Merged

feat: generate actions, and verify every kind's bindings against the SDK#38
ShocOne merged 2 commits into
mainfrom
feat/actions

Conversation

@ShocOne

@ShocOne ShocOne commented Jul 29, 2026

Copy link
Copy Markdown
Member

Phase 5.7 of the phase 5 plan. Base is main.

Actions are the third most-used block kind in the reference provider — 43 of them against 4 list resources — and the simplest, because an action has nothing to reconcile. verify reports 21/21 with no orphans, up from 17, and the provider now satisfies ProviderWithActions.

Three files per action against a resource's five: no construct.go, because an action sends its arguments as call parameters rather than a request body, and no state.go, because it writes nothing back. The pilot gains thousandeyes_disable_endpoint_agent, backed by the pinned SDK's real DisableEndpointAgent.

Generating a real action showed two of my earlier rules were wrong

  • Expands() returned true for an action. The reasoning was "an action sends values to the API" — it does, but as call arguments, and the emitter generates no construct function for one, so there is no body for an expand to build into. Now resource-only.
  • The flatten requirement was universal"every kind reads". An action does not: InvokeResponse has no field to carry a result, so a flatten would convert into somewhere that does not exist. Requiring one made the first action unrepresentable. There is now a Flattens() predicate, and a flatten on an action attribute is refused rather than ignored.

Three silent-drop bugs

  1. LoadDir merged Resources and DataSources and not Actions. Every action in a split blueprint vanished with no error whatsoever. Found only because the first emit wrote nothing.

  2. internal/sdkbind walked bp.Resources and nothing else. Data source bindings have been unverified since 5.2 and list facets since 5.6 — three quarters of the bindings in a blueprint were unchecked, while the command printed a reassuring count and even said "1 resource binding set(s)" while doing it. The accessor check is extracted, verifyOperation takes a key rather than a resource, and data sources, list facets and actions each get a verifier. A list facet's element type is checked too, since identityFrom and displayNameFrom name fields on one element and a typo there is a compile error in someone else's provider. 67 bindings now verified. I confirmed it bites by renaming the action's SDK method:

    disable_endpoint_agent: binding.invoke.method: endpoint_agents.EndpointAgents has no
    method DisableEndpointAgentTypo (did you mean: DisableEndpointAgent?)
    
  3. interop export said nothing about actions, so an entire block kind vanished from the exported document in silence — the one failure mode the note mechanism exists to prevent. Reported once for the kind rather than per action, since the loss is that the format has no such concept.

There was also a fourth, smaller one: accessorChain required the receiver to be literally "r", which is why the widened checks first reported every non-resource accessor as "cannot be verified" rather than verifying it. The receiver name is whatever the generated method uses (d, l, a), so it is skipped rather than checked; the field chain from client onwards is the part that resolves, and that is where a wrong accessor is actually caught.

Support addition

client.ForAction, because action.ConfigureRequest is its own type — the resource helper cannot serve it, and the three request types share no interface to write one generic helper against. The doc comment says so rather than leaving the duplication looking accidental.

Verification

  • go test ./... clean; golangci-lint run 0 issues; gofumpt clean (via go run, as CI does).
  • verify 21/21 no orphans; pilot builds and vets; bindings 67/67 against the pinned SDK; probe -mode verify 39/39; merge idempotent.
  • interfaces.go in the pilot asserts ProviderWithActions alongside the other two, so "the generated registries are what make this work" stays a build failure if it stops being true.
  • New internal/blueprint/action_test.go: the Expands/Flattens correction stated directly, a refused flatten, six action refusals, and the alias-namespace collision across kinds.

Note on the interruption

The full suite reported internal/sdkbind failing after 607s partway through. That turned out to be a corrupted local Go build cache, not the action — sdkbind passes in ~50s once cleared. Worth saying because it briefly looked like the action's binding was at fault, and it was not.

5.4b is still tracked

Not forgotten, and I want to correct something I said about it: I framed it as needing a fully curated second pilot resource, which over-stated the cost. 5.3b already established a cheaper pattern — ingest a resource from the committed spec, graft the pilot provider block on, emit, and compile it in a scratch directory. That gives genuine end-to-end coverage without committing a second pilot resource. So 5.4b is more tractable than I implied; the remaining work is the IR constraint fields, ingest extraction, render mapping, and regexp.MustCompile vars for RegexMatches.

🤖 Generated with Claude Code

ShocOne added 2 commits July 29, 2026 15:08
Phase 5.7. Actions are the third most-used block kind in
terraform-provider-microsoft365 -- 43 of them against 4 list resources -- and the simplest,
because an action has nothing to reconcile. Three files per action against a resource's
five: no construct.go, because an action sends its arguments as call parameters rather than
a request body, and no state.go, because it writes nothing back. The pilot gains
thousandeyes_disable_endpoint_agent, backed by the pinned SDK's DisableEndpointAgent.

Two of my earlier per-kind rules were wrong, and generating a real action is what showed it.

BlockKind.Expands returned true for an action, on the reasoning that an action sends values
to the API. It does -- as call arguments -- but the emitter generates no construct function
for one, so there is no body for an expand to build into. Expands is now resource-only, and
an action that genuinely POSTs a payload is a deliberate extension rather than something
this quietly claimed to support.

The flatten requirement was universal: "every kind reads". An action does not.
InvokeResponse has no field to carry a result, so a flatten conversion would convert into
somewhere that does not exist -- and requiring one made the first action unrepresentable.
There is now a Flattens predicate, and a flatten on an action attribute is refused rather
than ignored.

Three silent-drop bugs found and fixed:

LoadDir merged Resources and DataSources and not Actions, so every action in a split
blueprint vanished with no error at all. Found because the first emit wrote nothing.

internal/sdkbind walked bp.Resources and nothing else. Data source bindings have been
unverified since phase 5.2 and list facets since 5.6, so three quarters of the bindings in a
blueprint were unchecked while the command reported a reassuring count -- it even said
"resource binding set(s)" while doing it. The accessor check is extracted, the operation
check takes a key rather than a resource, and data sources, list facets and actions each get
a verifier. A list facet's element type is checked too, because identityFrom and
displayNameFrom name fields on one element and a typo there is a compile error in somebody
else's provider. 67 bindings now verified where the count was previously partial; confirmed
it bites by renaming the action's SDK method and watching it fail with a suggestion.

accessorChain required the receiver to be literally "r", which is why the widened checks
first reported every non-resource accessor as unverifiable rather than verified. The
receiver name is whatever the generated method uses, so it is skipped rather than checked;
the field chain from client onwards is the part that is resolved, and that is where a wrong
accessor is actually caught.

interop export said nothing about actions, so an entire block kind vanished from the
exported document in silence. Reported once for the kind rather than per action, since the
loss is that the format has no such concept.

client.ForAction is added to the hand-written support package: action.ConfigureRequest is its
own type, so the resource helper cannot serve it, and the three request types share no
interface to write one generic helper against.
te is the import alias the pilot's hand-written client package gives the SDK
(te "github.com/deploymenttheory/go-sdk-thousandeyes/thousandeyes"). codespell reads it as a
run-together "the", "be", "we" or "to".

Surfaced because this branch edits client.go to add ForAction, and super-linter reads changed
files only -- the alias has been there since the pilot existed. Added to ignore-words-list
rather than skipping the whole pilot directory: the generated output there is not worth
spell-checking, but the hand-written support packages are ours and their prose is.

Swept the rest of the tree under the same exclusions CI applies; nothing else outstanding.
@ShocOne
ShocOne merged commit ff71f1a into main Jul 30, 2026
16 checks passed
ShocOne added a commit that referenced this pull request Jul 30, 2026
feat: generate actions, and verify every kind's bindings against the SDK
@ShocOne
ShocOne deleted the feat/actions branch July 30, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant