Skip to content

fix: preserve scoped infra plan hashes#716

Merged
intel352 merged 1 commit into
mainfrom
fix/wfctl-scoped-plan-hash
May 19, 2026
Merged

fix: preserve scoped infra plan hashes#716
intel352 merged 1 commit into
mainfrom
fix/wfctl-scoped-plan-hash

Conversation

@intel352
Copy link
Copy Markdown
Contributor

@intel352 intel352 commented May 19, 2026

Summary

  • Persist the wfctl infra plan --include resource scope into saved plan.json files.
  • Reuse that persisted scope during wfctl infra apply --plan stale-hash validation.
  • Add regression coverage for saved scoped plans with extra unscoped resources in the config.

Verification

  • GOWORK=off go test ./cmd/wfctl -run TestInfraApplyConsumesScopedPlan -count=1 failed before the fix with plan stale: config hash mismatch.
  • GOWORK=off go test ./cmd/wfctl -run 'TestInfraApplyConsumesScopedPlan|TestInfraApplyConsumesPlan|TestInfraApplyPlanSkipBootstrap|TestApplyInclude' -count=1\n- GOWORK=off go test ./interfaces -count=1\n- GOWORK=off go test ./cmd/wfctl -run 'TestInfraApply|TestInfraPlan|TestApplyInclude|TestPlanInclude|TestIaCPlan|TestParseInfraConfig_AutoBootstrap' -count=1\n- GOWORK=off go build ./cmd/wfctl\n- git diff --check

Copilot AI review requested due to automatic review settings May 19, 2026 15:25
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

This PR fixes a stale-hash false-positive for plans generated with wfctl infra plan --include=<subset>. Previously the scope wasn't persisted in the plan file, so applying the plan against a config containing additional (unscoped) resources rehashed the full desired set and rejected the plan as stale. The fix persists the include scope on the plan and reapplies it before rehashing.

Changes:

  • Add Include []string to IaCPlan (JSON-roundtrippable, sorted) recording the plan-time --include scope.
  • During infra apply --plan, reconstruct the include set, validate it, and filter both desired specs and current state to that scope before computing the comparison hash.
  • Add unit (JSON roundtrip) and integration (TestInfraApplyConsumesScopedPlan) tests.

Reviewed changes

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

Show a summary per file
File Description
interfaces/iac_state.go New Include field on IaCPlan with docstring describing semantics.
interfaces/iac_state_test.go JSON roundtrip test for the new field.
cmd/wfctl/infra.go Persist scope at plan time; rehydrate, validate, and filter scope at apply time before stale-hash check.
cmd/wfctl/infra_apply_include.go New sortedIncludeNames / includeSetFromNames helpers to convert between persisted slice and runtime set.
cmd/wfctl/infra_apply_plan_test.go Integration test exercising scoped plan apply against a config with additional unscoped resources.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

❌ Patch coverage is 92.00000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cmd/wfctl/infra.go 66.66% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 19, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:274: parsing iteration count: invalid syntax
baseline-bench.txt:297068: parsing iteration count: invalid syntax
baseline-bench.txt:558240: parsing iteration count: invalid syntax
baseline-bench.txt:855532: parsing iteration count: invalid syntax
baseline-bench.txt:1155632: parsing iteration count: invalid syntax
baseline-bench.txt:1477923: parsing iteration count: invalid syntax
benchmark-results.txt:274: parsing iteration count: invalid syntax
benchmark-results.txt:289187: parsing iteration count: invalid syntax
benchmark-results.txt:547323: parsing iteration count: invalid syntax
benchmark-results.txt:844555: parsing iteration count: invalid syntax
benchmark-results.txt:1105433: parsing iteration count: invalid syntax
benchmark-results.txt:1618758: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ baseline-bench.txt │       benchmark-results.txt        │
                            │       sec/op       │    sec/op     vs base              │
InterpreterCreation-4               7.740m ± 59%   7.913m ± 61%       ~ (p=0.937 n=6)
ComponentLoad-4                     3.655m ± 10%   3.594m ±  1%  -1.65% (p=0.002 n=6)
ComponentExecute-4                  1.934µ ±  2%   1.978µ ±  2%  +2.28% (p=0.009 n=6)
PoolContention/workers-1-4          1.109µ ±  3%   1.115µ ±  3%       ~ (p=1.000 n=6)
PoolContention/workers-2-4          1.088µ ±  2%   1.100µ ±  0%       ~ (p=0.065 n=6)
PoolContention/workers-4-4          1.074µ ±  1%   1.090µ ±  1%  +1.49% (p=0.011 n=6)
PoolContention/workers-8-4          1.080µ ±  1%   1.106µ ±  2%  +2.41% (p=0.002 n=6)
PoolContention/workers-16-4         1.077µ ±  0%   1.095µ ±  2%  +1.72% (p=0.002 n=6)
ComponentLifecycle-4                3.586m ±  0%   3.668m ±  2%  +2.27% (p=0.002 n=6)
SourceValidation-4                  2.307µ ±  1%   2.375µ ±  5%  +2.93% (p=0.004 n=6)
RegistryConcurrent-4                805.0n ±  3%   819.8n ±  4%       ~ (p=0.485 n=6)
LoaderLoadFromString-4              3.595m ±  0%   3.812m ±  6%  +6.02% (p=0.002 n=6)
geomean                             18.82µ         19.18µ        +1.92%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.937 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=1.000 n=6)
ComponentExecute-4                  1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4         1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                2.183Mi ± 0%   2.183Mi ± 0%       ~ (p=0.331 n=6)
SourceValidation-4                  1.984Ki ± 0%   1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                1.133Ki ± 0%   1.133Ki ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4              2.182Mi ± 0%   2.182Mi ± 0%       ~ (p=0.846 n=6)
geomean                             15.25Ki        15.25Ki       +0.00%
¹ all samples are equal

                            │ baseline-bench.txt │        benchmark-results.txt        │
                            │     allocs/op      │  allocs/op   vs base                │
InterpreterCreation-4                15.68k ± 0%   15.68k ± 0%       ~ (p=1.000 n=6)
ComponentLoad-4                      18.02k ± 0%   18.02k ± 0%       ~ (p=1.000 n=6)
ComponentExecute-4                    25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4           25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                 18.07k ± 0%   18.07k ± 0%       ~ (p=1.000 n=6) ¹
SourceValidation-4                    32.00 ± 0%    32.00 ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                  2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4               18.06k ± 0%   18.06k ± 0%       ~ (p=1.000 n=6) ¹
geomean                               183.3         183.3       +0.00%
¹ all samples are equal

pkg: github.com/GoCodeAlone/workflow/middleware
                                  │ baseline-bench.txt │       benchmark-results.txt       │
                                  │       sec/op       │   sec/op     vs base              │
CircuitBreakerDetection-4                  285.8n ± 0%   298.4n ± 5%  +4.37% (p=0.002 n=6)
CircuitBreakerExecution_Success-4          21.47n ± 0%   21.54n ± 1%       ~ (p=0.071 n=6)
CircuitBreakerExecution_Failure-4          66.10n ± 0%   66.44n ± 0%  +0.51% (p=0.002 n=6)
geomean                                    74.03n        75.30n       +1.72%

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │        B/op        │    B/op     vs base                │
CircuitBreakerDetection-4                 144.0 ± 0%     144.0 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │     allocs/op      │ allocs/op   vs base                │
CircuitBreakerDetection-4                 1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │       sec/op       │    sec/op     vs base              │
IaCStateBackend_InProcess-4              309.7n ± 26%   315.6n ± 27%       ~ (p=0.167 n=6)
IaCStateBackend_GRPC-4                   9.500m ±  3%   9.904m ±  5%  +4.25% (p=0.004 n=6)
JQTransform_Simple-4                     656.9n ± 56%   694.4n ± 31%       ~ (p=0.240 n=6)
JQTransform_ObjectConstruction-4         1.630µ ±  0%   1.580µ ±  4%  -3.07% (p=0.002 n=6)
JQTransform_ArraySelect-4                3.582µ ±  1%   3.732µ ±  5%  +4.20% (p=0.002 n=6)
JQTransform_Complex-4                    39.81µ ±  0%   40.96µ ±  1%  +2.88% (p=0.002 n=6)
JQTransform_Throughput-4                 1.971µ ±  1%   2.019µ ±  3%       ~ (p=0.078 n=6)
SSEPublishDelivery-4                     70.17n ±  1%   70.48n ±  1%  +0.44% (p=0.041 n=6)
geomean                                  3.973µ         4.065µ        +2.31%

                                 │ baseline-bench.txt │         benchmark-results.txt         │
                                 │        B/op        │     B/op       vs base                │
IaCStateBackend_InProcess-4              416.0 ± 0%       416.0 ±  0%       ~ (p=1.000 n=6) ¹
IaCStateBackend_GRPC-4                 5.883Mi ± 9%     5.759Mi ± 17%       ~ (p=0.699 n=6)
JQTransform_Simple-4                   1.273Ki ± 0%     1.273Ki ±  0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4       1.773Ki ± 0%     1.773Ki ±  0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4              2.625Ki ± 0%     2.625Ki ±  0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                  16.22Ki ± 0%     16.22Ki ±  0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4               1.984Ki ± 0%     1.984Ki ±  0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%       0.000 ±  0%       ~ (p=1.000 n=6) ¹
geomean                                             ²                  -0.27%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │        benchmark-results.txt        │
                                 │     allocs/op      │  allocs/op   vs base                │
IaCStateBackend_InProcess-4              2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=6) ¹
IaCStateBackend_GRPC-4                  6.838k ± 0%     6.837k ± 0%       ~ (p=0.937 n=6)
JQTransform_Simple-4                     10.00 ± 0%      10.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4         15.00 ± 0%      15.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4                30.00 ± 0%      30.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                    324.0 ± 0%      324.0 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4                 17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²                -0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │       sec/op       │    sec/op     vs base              │
SchemaValidation_Simple-4                   1.100µ ± 16%   1.144µ ± 15%       ~ (p=0.370 n=6)
SchemaValidation_AllFields-4                1.658µ ±  1%   1.683µ ±  4%       ~ (p=0.093 n=6)
SchemaValidation_FormatValidation-4         1.591µ ±  1%   1.577µ ±  2%       ~ (p=0.132 n=6)
SchemaValidation_ManySchemas-4              1.826µ ± 13%   1.821µ ±  2%       ~ (p=1.000 n=6)
geomean                                     1.517µ         1.533µ        +1.06%

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │        B/op        │    B/op     vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │     allocs/op      │ allocs/op   vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │       sec/op       │    sec/op     vs base               │
EventStoreAppend_InMemory-4                1.185µ ± 14%   1.342µ ± 20%        ~ (p=0.310 n=6)
EventStoreAppend_SQLite-4                  1.395m ±  5%   1.239m ±  5%  -11.16% (p=0.002 n=6)
GetTimeline_InMemory/events-10-4           13.71µ ±  4%   13.62µ ±  2%        ~ (p=0.937 n=6)
GetTimeline_InMemory/events-50-4           73.71µ ± 16%   76.38µ ±  4%   +3.62% (p=0.045 n=6)
GetTimeline_InMemory/events-100-4          123.8µ ±  1%   124.6µ ±  2%        ~ (p=0.937 n=6)
GetTimeline_InMemory/events-500-4          640.8µ ±  2%   633.3µ ±  6%        ~ (p=0.394 n=6)
GetTimeline_InMemory/events-1000-4         1.304m ±  2%   1.299m ±  1%        ~ (p=0.589 n=6)
GetTimeline_SQLite/events-10-4             107.7µ ±  2%   108.5µ ±  8%        ~ (p=0.093 n=6)
GetTimeline_SQLite/events-50-4             246.9µ ±  1%   254.3µ ±  2%   +3.00% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            418.2µ ±  1%   421.7µ ±  1%   +0.82% (p=0.015 n=6)
GetTimeline_SQLite/events-500-4            1.781m ±  0%   1.809m ±  2%   +1.54% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.446m ±  1%   3.511m ±  1%   +1.89% (p=0.002 n=6)
geomean                                    218.4µ         220.4µ         +0.88%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  770.0 ± 8%     771.0 ± 9%       ~ (p=0.554 n=6)
EventStoreAppend_SQLite-4                  1.985Ki ± 2%   1.985Ki ± 2%       ~ (p=0.727 n=6)
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%   7.953Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%   46.62Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%   94.48Ki ± 0%       ~ (p=1.000 n=6)
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%   472.8Ki ± 0%       ~ (p=1.000 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ± 0%       ~ (p=1.000 n=6)
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%   16.74Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%   87.14Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%   175.4Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%   846.1Ki ± 0%       ~ (p=0.636 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%       ~ (p=1.000 n=6)
geomean                                    67.20Ki        67.21Ki       +0.01%
¹ all samples are equal

                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │     allocs/op      │  allocs/op   vs base                │
EventStoreAppend_InMemory-4                  7.000 ± 0%    7.000 ± 0%       ~ (p=1.000 n=6) ¹
EventStoreAppend_SQLite-4                    53.00 ± 0%    53.00 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-10-4             125.0 ± 0%    125.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4             653.0 ± 0%    653.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4           1.306k ± 0%   1.306k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4           6.514k ± 0%   6.514k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-1000-4          13.02k ± 0%   13.02k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-10-4               382.0 ± 0%    382.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4              1.852k ± 0%   1.852k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4             3.681k ± 0%   3.681k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4             18.54k ± 0%   18.54k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-1000-4            37.29k ± 0%   37.29k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                     1.162k        1.162k       +0.00%
¹ all samples are equal

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

@intel352 intel352 force-pushed the fix/wfctl-scoped-plan-hash branch from 509c95d to 3b4cf8c Compare May 19, 2026 15:40
@intel352 intel352 merged commit df15764 into main May 19, 2026
28 checks passed
@intel352 intel352 deleted the fix/wfctl-scoped-plan-hash branch May 19, 2026 15:53
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