Skip to content

feat(iac): step.iac_provider_* general steps — stateless two-phase (infra-admin migration PR-2)#837

Merged
intel352 merged 16 commits into
mainfrom
feat/iac-provider-steps-2026-06-02T0104
Jun 2, 2026
Merged

feat(iac): step.iac_provider_* general steps — stateless two-phase (infra-admin migration PR-2)#837
intel352 merged 16 commits into
mainfrom
feat/iac-provider-steps-2026-06-02T0104

Conversation

@intel352
Copy link
Copy Markdown
Contributor

@intel352 intel352 commented Jun 2, 2026

PR-2 of the infra-admin plugin migration (stacks on PR-1 #836, merged). Six general IaC-plugin-aware pipeline steps that resolve an interfaces.IaCProvider from the modular service registry (registered by PR-1's WiringHook) via a provider: <module-name> config key.

Tasks (TDD, one commit each)

  • T4 step.iac_provider_list — list current resource statuses.
  • T5 step.iac_provider_catalog — regions via the providerclient.RegionListerProvider accessor (static fallback when unadvertised) + Capabilities() types. Live-catalog source for dropdown forms.
  • T6 step.iac_provider_plan — stateless: DesiredStateHash (no-op env resolver — guards the v1.1 T3 regression) → returns {plan, desired_hash}.
  • T7 step.iac_provider_apply — stateless two-phase: recompute hash from current state, reject mismatch (tamper/drift guard), else ApplyPlanWithHooks.
  • T8 step.iac_provider_destroy + step.iac_provider_drift (drift via DriftDetectorProvider accessor; supported:false fallback).
  • T9 register all 6 in the builtin platform plugin + type_registry + schema.

Deviation (within Task 7): module/ cannot import iac/wfctlhelpers directly (wfctlhelpers/state.go imports module/ → cycle). Added iac/applydispatch — a thin re-export shim (type alias + one-line delegate, zero logic) so the apply step reaches ApplyPlanWithHooks without the cycle. No new feature/PR; ADR 0013/0015 unchanged.

ADR 0013 (steps bind interfaces.IaCProvider) + 0015 (stateless two-phase). Existing PlatformProvider step.iac_* untouched.

🤖 Generated with Claude Code

intel352 and others added 7 commits June 2, 2026 02:59
…lists resource statuses)

Adds IaCProviderListStep with resolveIaCProvider shared helper. Factory
requires `provider` config key; calls provider.Status(ctx, refs) with
optional ref list; returns {provider, resources[], count}. Unregistered
provider surfaces "not registered" error. Three tests cover happy path,
unregistered provider, and missing-config factory guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ies, static fallback)

IaCProviderCatalogStep type-asserts to providerclient.RegionListerProvider;
calls RegionLister().ListRegions when advertised (source:"live") and falls
back to a 10-region static list when the accessor returns nil or the
provider does not implement the interface (source:"static"). Merges
Capabilities() for resource types. Four tests cover live, no-lister,
nil-lister, and missing-config factory guard paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e hash)

IaCProviderPlanStep fetches current state via provider.Status, computes
computeDesiredStateHash (inlined from wfctlhelpers.DesiredStateHash with
no-op env resolver to break module→wfctlhelpers→module import cycle), calls
provider.Plan, attaches DesiredHash to the plan, and returns
{plan, desired_hash, provider}. Four tests: happy path returns plan + hash,
env-var-value change does NOT change hash (no-op resolver guard), unregistered
provider surfaces "not registered", missing 'provider' config fails factory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ash-guard apply)

IaCProviderApplyStep: recomputes DesiredStateHash from live Status, rejects
with "plan hash mismatch (state changed or plan tampered); re-plan" if hashes
diverge (apply never runs), else calls injected applyFn (prod: wfctlhelpers
.ApplyPlanWithHooks via function injection to break module→wfctlhelpers→module
cycle; wfctlhelpers/state.go imports module so direct import is impossible).

Adds iac/applydispatch bridge package as a re-export shim (considered but
insufficient — Go cycle detection spans the full graph; function injection is
the correct pattern). Five tests: match→applies, mismatch→rejected+no-apply,
provider error surfaced (not "denied"), missing provider guard, missing hash guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…drift

destroy: calls provider.Destroy(refs), returns {destroyed[], destroy_errors[],
provider}. Three tests: returns destroyed list, unregistered provider, missing
provider config.

drift: type-asserts to providerclient.DriftDetectorProvider; if accessor non-nil
→ DetectDriftWithSpecs (config-aware); else falls through to required DetectDrift;
required surface error → {supported:false, reason}. Returns {supported, any_drifted,
drifts[], count, provider}. Five tests: with detector (any_drifted=true), nil
detector (existence-only fallback), no interface (existence-only), DetectDrift
error → supported=false, missing provider config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pe registry, and schema

plugins/platform/plugin.go: adds iacProviderApplyFn wrapper (wfctlhelpers.ApplyPlanWithHooks
with empty hooks, called from plugin which can import wfctlhelpers); registers all 6 step
factories (list/catalog/plan/apply/destroy/drift) in StepFactories() and manifest StepTypes.

cmd/wfctl/type_registry.go: 6 StepTypeInfo entries under "platform plugin steps (iac provider)".

schema/schema.go: 6 new entries in coreModuleTypes (alphabetically sorted with existing iac_ steps).
schema/step_schema_builtins.go: 6 StepSchema entries with ConfigFields + Outputs in registerBuiltins().
schema/module_schema.go: 6 display-name entries in the inline step-type list.

plugins/platform/plugin_test.go: adds 6 new step types to TestStepFactories expected list.

wfctl validate accepts step.iac_provider_list in a pipeline config. All schema tests pass.
Pre-existing cmd/wfctl subprocess failures (TestFallbackRuns, strict-contracts) are unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…p schemas

The 6 new step type entries added in Task 9 expand the StepSchemaRegistry
which is serialized into the golden file used by TestEditorSchemasGoldenFile.
Regenerated via UPDATE_GOLDEN=1 go test ./schema/ -run TestEditorSchemasGoldenFile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 2, 2026 07:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new set of provider-agnostic IaC pipeline steps (step.iac_provider_*) that resolve an interfaces.IaCProvider from the modular service registry (registered in PR-1), enabling stateless two-phase plan/apply and related operations. This extends the platform plugin and CLI/type/schema registries so these steps are discoverable and usable across the engine and wfctl.

Changes:

  • Introduces six new platform steps: list, catalog, plan, apply (two-phase hash guard), destroy, drift—backed by interfaces.IaCProvider service resolution.
  • Registers the new step types across platform plugin factories/tests, schema registries, and wfctl’s known step type registry.
  • Adds an iac/applydispatch shim intended to re-export wfctlhelpers.ApplyPlanWithHooks without introducing import cycles.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
schema/testdata/editor-schemas.golden.json Adds editor schema entries for the new step.iac_provider_* steps
schema/step_schema_builtins.go Registers builtin step schemas for the six new IaCProvider steps
schema/schema.go Adds new step types to the core module type allowlist
schema/module_schema.go Adds new step types to the builtin module schema registry
plugins/platform/plugin.go Registers new step types and factories in the platform plugin
plugins/platform/plugin_test.go Extends platform plugin step factory coverage to include new steps
module/pipeline_step_iac_provider_plan.go Implements step.iac_provider_plan + hashing helpers
module/pipeline_step_iac_provider_plan_test.go Tests planning + hash stability behavior
module/pipeline_step_iac_provider_list.go Implements step.iac_provider_list and IaCProvider service resolution helper
module/pipeline_step_iac_provider_list_test.go Tests list step behavior and provider resolution errors
module/pipeline_step_iac_provider_drift.go Implements drift step with optional drift-detector fast-path
module/pipeline_step_iac_provider_destroy.go Implements destroy step using IaCProvider.Destroy
module/pipeline_step_iac_provider_destroy_drift_test.go Tests destroy + drift behaviors and fallbacks
module/pipeline_step_iac_provider_catalog.go Implements catalog step (regions + capability types) with static fallback
module/pipeline_step_iac_provider_catalog_test.go Tests live vs static region sources and types output
module/pipeline_step_iac_provider_apply.go Implements stateless two-phase apply with injected apply dispatcher
module/pipeline_step_iac_provider_apply_test.go Tests apply hash-guard acceptance/rejection and dispatcher error surfacing
iac/applydispatch/applydispatch.go Adds a shim re-export for ApplyPlanWithHooks/ApplyPlanHooks
cmd/wfctl/type_registry.go Registers new step types/config keys in wfctl’s known type registry

Comment on lines +211 to +215
// Step 4: SHA-256 over the canonical JSON.
data, err := json.Marshal(resolved)
if err != nil {
return "hash-error"
}
Comment on lines +68 to +85
// Existence-only drift via the required DetectDrift method.
drifts, driftErr := provider.DetectDrift(ctx, s.refs)
if driftErr != nil {
// ErrProviderMethodUnimplemented from the required surface means the plugin
// wired neither path — surface as unsupported, not as an error. The step
// intentionally swallows the error here and converts it to structured output
// so callers can gate on {supported: false} without pipeline failure.
return &StepResult{Output: map[string]any{ //nolint:nilerr
"provider": s.provider,
"supported": false,
"reason": driftErr.Error(),
}}, nil
}
return driftResult(s.provider, drifts, true), nil
}

// driftResult builds the step output map from a drift detection result.
func driftResult(providerName string, drifts []interfaces.DriftResult, supported bool) *StepResult {
Comment on lines +45 to +66
// Optional: list of refs to query; nil means pass empty slice to Status
// (providers should return all resources when refs is empty).
var refs []interfaces.ResourceRef
if rawRefs, ok := cfg["refs"]; ok {
if refList, ok := rawRefs.([]any); ok {
for _, r := range refList {
if rm, ok := r.(map[string]any); ok {
ref := interfaces.ResourceRef{}
if n, ok := rm["name"].(string); ok {
ref.Name = n
}
if t, ok := rm["type"].(string); ok {
ref.Type = t
}
if pid, ok := rm["provider_id"].(string); ok {
ref.ProviderID = pid
}
refs = append(refs, ref)
}
}
}
}
Comment on lines +100 to +110
// First hash: env var set to "secret1".
os.Setenv("DB_PASSWORD", "secret1")
result1, err := step.Execute(context.Background(), &module.PipelineContext{})
if err != nil {
t.Fatalf("Execute (run 1) error: %v", err)
}
hash1 := result1.Output["desired_hash"].(string)

// Second hash: env var changed to "secret2".
os.Setenv("DB_PASSWORD", "secret2")
result2, err := step.Execute(context.Background(), &module.PipelineContext{})
Comment on lines 6 to +24
import (
"context"

"github.com/GoCodeAlone/modular"
"github.com/GoCodeAlone/workflow/handlers"
"github.com/GoCodeAlone/workflow/iac/wfctlhelpers"
"github.com/GoCodeAlone/workflow/interfaces"
"github.com/GoCodeAlone/workflow/module"
"github.com/GoCodeAlone/workflow/plugin"
"github.com/GoCodeAlone/workflow/schema"
)

// iacProviderApplyFn is the apply dispatch function passed to
// NewIaCProviderApplyStepFactory. It wraps wfctlhelpers.ApplyPlanWithHooks
// with an empty hooks struct so the step's function signature
// (ctx, provider, plan) is satisfied without the step importing wfctlhelpers.
func iacProviderApplyFn(ctx context.Context, p interfaces.IaCProvider, plan *interfaces.IaCPlan) (*interfaces.ApplyResult, error) {
return wfctlhelpers.ApplyPlanWithHooks(ctx, p, plan, wfctlhelpers.ApplyPlanHooks{})
}
Comment on lines +12 to +19
// IaCApplyFn is the function signature for the v2 apply dispatch helper.
// Matches wfctlhelpers.ApplyPlanWithHooks exactly so the platform plugin can
// pass that function directly, and tests can inject a stub without importing
// wfctlhelpers (which would create a module→wfctlhelpers→module cycle via
// wfctlhelpers/state.go).
type IaCApplyFn func(ctx context.Context, p interfaces.IaCProvider, plan *interfaces.IaCPlan, hooks any) (*interfaces.ApplyResult, error)

// ─── step.iac_provider_apply ─────────────────────────────────────────────────
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:308: parsing iteration count: invalid syntax
baseline-bench.txt:367737: parsing iteration count: invalid syntax
baseline-bench.txt:737413: parsing iteration count: invalid syntax
baseline-bench.txt:1077191: parsing iteration count: invalid syntax
baseline-bench.txt:1417419: parsing iteration count: invalid syntax
baseline-bench.txt:1807591: parsing iteration count: invalid syntax
benchmark-results.txt:308: parsing iteration count: invalid syntax
benchmark-results.txt:332127: parsing iteration count: invalid syntax
benchmark-results.txt:663397: parsing iteration count: invalid syntax
benchmark-results.txt:1009100: parsing iteration count: invalid syntax
benchmark-results.txt:1337907: parsing iteration count: invalid syntax
benchmark-results.txt:1638940: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ benchmark-results.txt │
                            │        sec/op         │
InterpreterCreation-4                  10.04m ± 49%
ComponentLoad-4                        3.715m ±  1%
ComponentExecute-4                     1.937µ ±  1%
PoolContention/workers-1-4             1.101µ ±  1%
PoolContention/workers-2-4             1.096µ ±  3%
PoolContention/workers-4-4             1.093µ ±  1%
PoolContention/workers-8-4             1.088µ ±  3%
PoolContention/workers-16-4            1.088µ ±  2%
ComponentLifecycle-4                   3.608m ±  1%
SourceValidation-4                     2.324µ ±  1%
RegistryConcurrent-4                   803.8n ±  4%
LoaderLoadFromString-4                 3.632m ±  2%
geomean                                19.35µ

                            │ benchmark-results.txt │
                            │         B/op          │
InterpreterCreation-4                  2.027Mi ± 0%
ComponentLoad-4                        2.180Mi ± 0%
ComponentExecute-4                     1.203Ki ± 0%
PoolContention/workers-1-4             1.203Ki ± 0%
PoolContention/workers-2-4             1.203Ki ± 0%
PoolContention/workers-4-4             1.203Ki ± 0%
PoolContention/workers-8-4             1.203Ki ± 0%
PoolContention/workers-16-4            1.203Ki ± 0%
ComponentLifecycle-4                   2.183Mi ± 0%
SourceValidation-4                     1.984Ki ± 0%
RegistryConcurrent-4                   1.133Ki ± 0%
LoaderLoadFromString-4                 2.182Mi ± 0%
geomean                                15.25Ki

                            │ benchmark-results.txt │
                            │       allocs/op       │
InterpreterCreation-4                   15.68k ± 0%
ComponentLoad-4                         18.02k ± 0%
ComponentExecute-4                       25.00 ± 0%
PoolContention/workers-1-4               25.00 ± 0%
PoolContention/workers-2-4               25.00 ± 0%
PoolContention/workers-4-4               25.00 ± 0%
PoolContention/workers-8-4               25.00 ± 0%
PoolContention/workers-16-4              25.00 ± 0%
ComponentLifecycle-4                    18.07k ± 0%
SourceValidation-4                       32.00 ± 0%
RegistryConcurrent-4                     2.000 ± 0%
LoaderLoadFromString-4                  18.06k ± 0%
geomean                                  183.3

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                            │ baseline-bench.txt │
                            │       sec/op       │
InterpreterCreation-4               10.31m ± 62%
ComponentLoad-4                     3.408m ±  1%
ComponentExecute-4                  1.968µ ±  1%
PoolContention/workers-1-4          1.180µ ±  3%
PoolContention/workers-2-4          1.215µ ±  3%
PoolContention/workers-4-4          1.201µ ±  3%
PoolContention/workers-8-4          1.184µ ±  0%
PoolContention/workers-16-4         1.187µ ±  1%
ComponentLifecycle-4                3.448m ±  1%
SourceValidation-4                  2.213µ ±  1%
RegistryConcurrent-4                885.4n ±  6%
LoaderLoadFromString-4              3.483m ±  1%
geomean                             19.93µ

                            │ baseline-bench.txt │
                            │        B/op        │
InterpreterCreation-4               2.027Mi ± 0%
ComponentLoad-4                     2.180Mi ± 0%
ComponentExecute-4                  1.203Ki ± 0%
PoolContention/workers-1-4          1.203Ki ± 0%
PoolContention/workers-2-4          1.203Ki ± 0%
PoolContention/workers-4-4          1.203Ki ± 0%
PoolContention/workers-8-4          1.203Ki ± 0%
PoolContention/workers-16-4         1.203Ki ± 0%
ComponentLifecycle-4                2.183Mi ± 0%
SourceValidation-4                  1.984Ki ± 0%
RegistryConcurrent-4                1.133Ki ± 0%
LoaderLoadFromString-4              2.182Mi ± 0%
geomean                             15.25Ki

                            │ baseline-bench.txt │
                            │     allocs/op      │
InterpreterCreation-4                15.68k ± 0%
ComponentLoad-4                      18.02k ± 0%
ComponentExecute-4                    25.00 ± 0%
PoolContention/workers-1-4            25.00 ± 0%
PoolContention/workers-2-4            25.00 ± 0%
PoolContention/workers-4-4            25.00 ± 0%
PoolContention/workers-8-4            25.00 ± 0%
PoolContention/workers-16-4           25.00 ± 0%
ComponentLifecycle-4                 18.07k ± 0%
SourceValidation-4                    32.00 ± 0%
RegistryConcurrent-4                  2.000 ± 0%
LoaderLoadFromString-4               18.06k ± 0%
geomean                               183.3

pkg: github.com/GoCodeAlone/workflow/middleware
cpu: AMD EPYC 7763 64-Core Processor                
                                  │ benchmark-results.txt │
                                  │        sec/op         │
CircuitBreakerDetection-4                     287.0n ± 3%
CircuitBreakerExecution_Success-4             21.47n ± 0%
CircuitBreakerExecution_Failure-4             66.08n ± 0%
geomean                                       74.12n

                                  │ benchmark-results.txt │
                                  │         B/op          │
CircuitBreakerDetection-4                    144.0 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

                                  │ benchmark-results.txt │
                                  │       allocs/op       │
CircuitBreakerDetection-4                    1.000 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                  │ baseline-bench.txt │
                                  │       sec/op       │
CircuitBreakerDetection-4                  454.2n ± 0%
CircuitBreakerExecution_Success-4          59.82n ± 0%
CircuitBreakerExecution_Failure-4          66.24n ± 1%
geomean                                    121.6n

                                  │ baseline-bench.txt │
                                  │        B/op        │
CircuitBreakerDetection-4                 144.0 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │
                                  │     allocs/op      │
CircuitBreakerDetection-4                 1.000 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
cpu: AMD EPYC 7763 64-Core Processor                
                                 │ benchmark-results.txt │
                                 │        sec/op         │
IaCStateBackend_InProcess-4                 331.9n ± 20%
IaCStateBackend_GRPC-4                      9.650m ±  3%
JQTransform_Simple-4                        708.2n ± 28%
JQTransform_ObjectConstruction-4            1.497µ ±  2%
JQTransform_ArraySelect-4                   3.474µ ±  1%
JQTransform_Complex-4                       39.36µ ±  1%
JQTransform_Throughput-4                    1.838µ ±  1%
SSEPublishDelivery-4                        65.12n ±  0%
geomean                                     3.918µ

                                 │ benchmark-results.txt │
                                 │         B/op          │
IaCStateBackend_InProcess-4                 416.0 ± 0%
IaCStateBackend_GRPC-4                    5.821Mi ± 7%
JQTransform_Simple-4                      1.273Ki ± 0%
JQTransform_ObjectConstruction-4          1.773Ki ± 0%
JQTransform_ArraySelect-4                 2.625Ki ± 0%
JQTransform_Complex-4                     16.31Ki ± 0%
JQTransform_Throughput-4                  1.984Ki ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                 │ benchmark-results.txt │
                                 │       allocs/op       │
IaCStateBackend_InProcess-4                 2.000 ± 0%
IaCStateBackend_GRPC-4                     6.833k ± 0%
JQTransform_Simple-4                        10.00 ± 0%
JQTransform_ObjectConstruction-4            15.00 ± 0%
JQTransform_ArraySelect-4                   30.00 ± 0%
JQTransform_Complex-4                       328.0 ± 0%
JQTransform_Throughput-4                    17.00 ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                 │ baseline-bench.txt │
                                 │       sec/op       │
IaCStateBackend_InProcess-4              340.5n ± 27%
IaCStateBackend_GRPC-4                   9.725m ± 30%
JQTransform_Simple-4                     736.2n ± 19%
JQTransform_ObjectConstruction-4         1.445µ ±  1%
JQTransform_ArraySelect-4                3.124µ ±  1%
JQTransform_Complex-4                    35.31µ ±  0%
JQTransform_Throughput-4                 1.747µ ±  2%
SSEPublishDelivery-4                     76.35n ±  7%
geomean                                  3.884µ

                                 │ baseline-bench.txt │
                                 │        B/op        │
IaCStateBackend_InProcess-4              416.0 ± 0%
IaCStateBackend_GRPC-4                 5.785Mi ± 7%
JQTransform_Simple-4                   1.273Ki ± 0%
JQTransform_ObjectConstruction-4       1.773Ki ± 0%
JQTransform_ArraySelect-4              2.625Ki ± 0%
JQTransform_Complex-4                  16.31Ki ± 0%
JQTransform_Throughput-4               1.984Ki ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │
                                 │     allocs/op      │
IaCStateBackend_InProcess-4              2.000 ± 0%
IaCStateBackend_GRPC-4                  6.865k ± 0%
JQTransform_Simple-4                     10.00 ± 0%
JQTransform_ObjectConstruction-4         15.00 ± 0%
JQTransform_ArraySelect-4                30.00 ± 0%
JQTransform_Complex-4                    328.0 ± 0%
JQTransform_Throughput-4                 17.00 ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
cpu: AMD EPYC 7763 64-Core Processor                
                                    │ benchmark-results.txt │
                                    │        sec/op         │
SchemaValidation_Simple-4                       1.121µ ± 5%
SchemaValidation_AllFields-4                    1.678µ ± 4%
SchemaValidation_FormatValidation-4             1.587µ ± 1%
SchemaValidation_ManySchemas-4                  1.856µ ± 5%
geomean                                         1.534µ

                                    │ benchmark-results.txt │
                                    │         B/op          │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

                                    │ benchmark-results.txt │
                                    │       allocs/op       │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                    │ baseline-bench.txt │
                                    │       sec/op       │
SchemaValidation_Simple-4                    1.019µ ± 2%
SchemaValidation_AllFields-4                 1.572µ ± 5%
SchemaValidation_FormatValidation-4          1.490µ ± 1%
SchemaValidation_ManySchemas-4               1.515µ ± 3%
geomean                                      1.379µ

                                    │ baseline-bench.txt │
                                    │        B/op        │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │
                                    │     allocs/op      │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
cpu: AMD EPYC 7763 64-Core Processor                
                                   │ benchmark-results.txt │
                                   │        sec/op         │
EventStoreAppend_InMemory-4                   1.181µ ± 27%
EventStoreAppend_SQLite-4                     1.548m ± 32%
GetTimeline_InMemory/events-10-4              14.09µ ±  4%
GetTimeline_InMemory/events-50-4              78.19µ ±  4%
GetTimeline_InMemory/events-100-4             161.3µ ±  3%
GetTimeline_InMemory/events-500-4             802.8µ ± 22%
GetTimeline_InMemory/events-1000-4            1.296m ±  1%
GetTimeline_SQLite/events-10-4                104.7µ ±  1%
GetTimeline_SQLite/events-50-4                245.2µ ±  1%
GetTimeline_SQLite/events-100-4               419.5µ ±  1%
GetTimeline_SQLite/events-500-4               1.782m ±  1%
GetTimeline_SQLite/events-1000-4              3.463m ±  1%
geomean                                       230.5µ

                                   │ benchmark-results.txt │
                                   │         B/op          │
EventStoreAppend_InMemory-4                     778.0 ± 8%
EventStoreAppend_SQLite-4                     1.986Ki ± 2%
GetTimeline_InMemory/events-10-4              7.953Ki ± 0%
GetTimeline_InMemory/events-50-4              46.62Ki ± 0%
GetTimeline_InMemory/events-100-4             94.48Ki ± 0%
GetTimeline_InMemory/events-500-4             472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4            944.3Ki ± 0%
GetTimeline_SQLite/events-10-4                16.74Ki ± 0%
GetTimeline_SQLite/events-50-4                87.14Ki ± 0%
GetTimeline_SQLite/events-100-4               175.4Ki ± 0%
GetTimeline_SQLite/events-500-4               846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4              1.639Mi ± 0%
geomean                                       67.26Ki

                                   │ benchmark-results.txt │
                                   │       allocs/op       │
EventStoreAppend_InMemory-4                     7.000 ± 0%
EventStoreAppend_SQLite-4                       53.00 ± 0%
GetTimeline_InMemory/events-10-4                125.0 ± 0%
GetTimeline_InMemory/events-50-4                653.0 ± 0%
GetTimeline_InMemory/events-100-4              1.306k ± 0%
GetTimeline_InMemory/events-500-4              6.514k ± 0%
GetTimeline_InMemory/events-1000-4             13.02k ± 0%
GetTimeline_SQLite/events-10-4                  382.0 ± 0%
GetTimeline_SQLite/events-50-4                 1.852k ± 0%
GetTimeline_SQLite/events-100-4                3.681k ± 0%
GetTimeline_SQLite/events-500-4                18.54k ± 0%
GetTimeline_SQLite/events-1000-4               37.29k ± 0%
geomean                                        1.162k

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                   │ baseline-bench.txt │
                                   │       sec/op       │
EventStoreAppend_InMemory-4                1.118µ ± 16%
EventStoreAppend_SQLite-4                  1.020m ±  5%
GetTimeline_InMemory/events-10-4           13.87µ ±  8%
GetTimeline_InMemory/events-50-4           74.60µ ±  3%
GetTimeline_InMemory/events-100-4          151.1µ ±  2%
GetTimeline_InMemory/events-500-4          639.0µ ± 20%
GetTimeline_InMemory/events-1000-4         1.201m ±  1%
GetTimeline_SQLite/events-10-4             80.79µ ±  2%
GetTimeline_SQLite/events-50-4             230.6µ ±  3%
GetTimeline_SQLite/events-100-4            437.2µ ±  2%
GetTimeline_SQLite/events-500-4            1.910m ±  2%
GetTimeline_SQLite/events-1000-4           3.722m ±  3%
geomean                                    211.3µ

                                   │ baseline-bench.txt │
                                   │        B/op        │
EventStoreAppend_InMemory-4                 763.0 ± 11%
EventStoreAppend_SQLite-4                 1.986Ki ±  1%
GetTimeline_InMemory/events-10-4          7.953Ki ±  0%
GetTimeline_InMemory/events-50-4          46.62Ki ±  0%
GetTimeline_InMemory/events-100-4         94.48Ki ±  0%
GetTimeline_InMemory/events-500-4         472.8Ki ±  0%
GetTimeline_InMemory/events-1000-4        944.3Ki ±  0%
GetTimeline_SQLite/events-10-4            16.74Ki ±  0%
GetTimeline_SQLite/events-50-4            87.14Ki ±  0%
GetTimeline_SQLite/events-100-4           175.4Ki ±  0%
GetTimeline_SQLite/events-500-4           846.1Ki ±  0%
GetTimeline_SQLite/events-1000-4          1.639Mi ±  0%
geomean                                   67.15Ki

                                   │ baseline-bench.txt │
                                   │     allocs/op      │
EventStoreAppend_InMemory-4                  7.000 ± 0%
EventStoreAppend_SQLite-4                    53.00 ± 0%
GetTimeline_InMemory/events-10-4             125.0 ± 0%
GetTimeline_InMemory/events-50-4             653.0 ± 0%
GetTimeline_InMemory/events-100-4           1.306k ± 0%
GetTimeline_InMemory/events-500-4           6.514k ± 0%
GetTimeline_InMemory/events-1000-4          13.02k ± 0%
GetTimeline_SQLite/events-10-4               382.0 ± 0%
GetTimeline_SQLite/events-50-4              1.852k ± 0%
GetTimeline_SQLite/events-100-4             3.681k ± 0%
GetTimeline_SQLite/events-500-4             18.54k ± 0%
GetTimeline_SQLite/events-1000-4            37.29k ± 0%
geomean                                     1.162k

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

intel352 and others added 6 commits June 2, 2026 03:51
Zero importers confirmed; plugins/platform/plugin.go calls
wfctlhelpers.ApplyPlanWithHooks directly via the injected iacProviderApplyFn
closure and never imported applydispatch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The unsupported/error path omitted any_drifted, drifts, and count, so
downstream type-assertions like result.Output["any_drifted"].(bool) would
panic.  Emit zero-value entries on the unsupported path to match the
normal-path schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The type had a wrong 4-parameter signature (hooks any) and was never
referenced anywhere; the factory's injected applyFn is typed inline with
the correct 3-parameter signature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Old name implied supported=false (Unsupported) but the test asserts
supported=true because nil detector falls through to provider.DetectDrift
which succeeds.  New name reflects the actual behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…llback_error

Covers the case where the provider implements RegionListerProvider and
returns a non-nil lister but ListRegions returns an error; the step must
fall back to static regions and set source=static_fallback_error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rage gate)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 2, 2026 08:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Comment on lines +69 to +88
drifts, driftErr := provider.DetectDrift(ctx, s.refs)
if driftErr != nil {
// ErrProviderMethodUnimplemented from the required surface means the plugin
// wired neither path — surface as unsupported, not as an error. The step
// intentionally swallows the error here and converts it to structured output
// so callers can gate on {supported: false} without pipeline failure.
// all_drifted, drifts, and count are included with zero values so that
// downstream type-assertions (e.g. result.Output["any_drifted"].(bool)) do
// not panic on the unsupported path.
return &StepResult{Output: map[string]any{ //nolint:nilerr
"provider": s.provider,
"supported": false,
"reason": driftErr.Error(),
"any_drifted": false,
"drifts": []map[string]any{},
"count": 0,
}}, nil
}
return driftResult(s.provider, drifts, true), nil
}
Comment on lines +3 to +10
import (
"context"
"fmt"

"github.com/GoCodeAlone/modular"
"github.com/GoCodeAlone/workflow/iac/providerclient"
"github.com/GoCodeAlone/workflow/interfaces"
)
Comment on lines +45 to +66
// Optional: list of refs to query; nil means pass empty slice to Status
// (providers should return all resources when refs is empty).
var refs []interfaces.ResourceRef
if rawRefs, ok := cfg["refs"]; ok {
if refList, ok := rawRefs.([]any); ok {
for _, r := range refList {
if rm, ok := r.(map[string]any); ok {
ref := interfaces.ResourceRef{}
if n, ok := rm["name"].(string); ok {
ref.Name = n
}
if t, ok := rm["type"].(string); ok {
ref.Type = t
}
if pid, ok := rm["provider_id"].(string); ok {
ref.ProviderID = pid
}
refs = append(refs, ref)
}
}
}
}
Comment on lines +2205 to +2211
Outputs: []StepOutputDef{
{Key: "provider", Type: "string", Description: "Provider service name"},
{Key: "supported", Type: "boolean", Description: "Whether drift detection is supported"},
{Key: "any_drifted", Type: "boolean", Description: "Whether any resource has drifted"},
{Key: "drifts", Type: "[]any", Description: "Per-resource drift results"},
{Key: "count", Type: "number", Description: "Number of resources checked"},
},
Comment on lines +100 to +114
// First hash: env var set to "secret1".
os.Setenv("DB_PASSWORD", "secret1")
result1, err := step.Execute(context.Background(), &module.PipelineContext{})
if err != nil {
t.Fatalf("Execute (run 1) error: %v", err)
}
hash1 := result1.Output["desired_hash"].(string)

// Second hash: env var changed to "secret2".
os.Setenv("DB_PASSWORD", "secret2")
result2, err := step.Execute(context.Background(), &module.PipelineContext{})
if err != nil {
t.Fatalf("Execute (run 2) error: %v", err)
}
hash2 := result2.Output["desired_hash"].(string)
intel352 and others added 3 commits June 2, 2026 04:17
…onstant fallback

A constant "hash-error" fallback on json.Marshal failure could silently
match across plan+apply and bypass the tamper/drift guard. Changed
computeDesiredStateHash to return (string, error); both plan and apply
Execute paths propagate the error and emit no plan/apply on failure.
Removed os.Setenv/os.Unsetenv in favor of t.Setenv (auto-restore, safe
under -shuffle).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… as supported:false

Previously ANY DetectDrift error was swallowed into {supported:false},
hiding network failures, provider crashes, etc. as a structured
unsupported-feature response. Now only errors.Is(err,
ErrProviderMethodUnimplemented) produces {supported:false}; any other
error is returned as an error (HTTP 5xx path). Renamed existing
Unsupported_DetectDriftError test to use the actual sentinel; added
TestIaCProviderDriftStep_Execute_RealError_Propagated for the new path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tly widening to list-all

If "refs" is present but the wrong type or contains non-map items, the
factory now returns a config error rather than silently falling through
to an unfiltered Status (list everything). Absent refs remains list-all.
Added three new tests: MalformedRefs_WrongTopType, MalformedRefs_WrongItemType,
AbsentRefs_ListsAll.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@intel352 intel352 merged commit 1bc4443 into main Jun 2, 2026
22 checks passed
@intel352 intel352 deleted the feat/iac-provider-steps-2026-06-02T0104 branch June 2, 2026 08:30
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.

2 participants