Skip to content

fix: preserve required secrets in infra plans#746

Merged
intel352 merged 2 commits into
mainfrom
codex/wfctl-required-secrets-runtime
May 21, 2026
Merged

fix: preserve required secrets in infra plans#746
intel352 merged 2 commits into
mainfrom
codex/wfctl-required-secrets-runtime

Conversation

@intel352
Copy link
Copy Markdown
Contributor

Summary

  • Treat top-level secrets.entries and module secrets.requires / secrets.generate keys as declared secrets during infra plan-time env expansion.
  • Keep externally supplied required secrets as literal ${VAR} references at plan time so F3 does not see secret literals and desired-state hashes stay stable.
  • Add regressions covering WFCOMPUTE_VALIDATION_TOKEN-style required secrets and hash stability.

Red/green proof

With fix reverted:

$ GOWORK=off go test ./cmd/wfctl -run 'Test(ParseInfraResourceSpecs_PreservesRequiredSecretVarsInUserData|BuildRuntimeOnlySecretKeys|ResolveSpecsAgainstState_RequiredSecretNotSubstituted)' -count=1\nFAIL — required secret resolved into user_data, desiredStateHash changed, and WFCOMPUTE_VALIDATION_TOKEN entered the plan.\n```\n\nWith fix restored:\n```\n$ GOWORK=off go test ./cmd/wfctl -run 'Test(ParseInfraResourceSpecs_Preserves.*Secret|BuildRuntimeOnlySecretKeys|ResolveSpecsAgainstState_.*SecretNotSubstituted)' -count=1\nok github.com/GoCodeAlone/workflow/cmd/wfctl 1.037s\n```\n\n## Verification\n```\n$ GOWORK=off go test ./cmd/wfctl -count=1\nok github.com/GoCodeAlone/workflow/cmd/wfctl 59.980s\n```\n\n## Notes\nBMW deploy is currently using `setup-wfctl` with `version: latest`, which resolves to the latest non-prerelease workflow release (`v0.60.9` at time of investigation). This fix needs to be included in a new non-prerelease wfctl release before BMW staging can consume it without changing the deploy pin.\n

Copilot AI review requested due to automatic review settings May 21, 2026 01:48
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 updates wfctl infra planning to treat more “declared secrets” as plan-time preserved ${VAR} references, preventing secret literals from entering infra plans and keeping desired-state hashes stable across plan/apply.

Changes:

  • Expand the set of preserved variables to include top-level secrets.entries and secrets.* modules (secrets.requires / secrets.generate) during infra plan-time env expansion.
  • Extend runtime-only secret blocking to cover externally supplied required secrets (not just generated non-infra_output secrets), so plan-time substitution won’t embed them into plans.
  • Add regressions covering required-secret preservation and desired-state hash stability.

Reviewed changes

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

Show a summary per file
File Description
cmd/wfctl/testdata/infra-with-env-var-refs.yaml Adds a secrets.requires module and references a required secret in user_data to exercise plan-time preservation.
cmd/wfctl/infra.go Replaces secretGenKeys with declaredSecretKeys and adds parsing for secret keys from top-level secrets and secrets.* modules.
cmd/wfctl/infra_resolve_state.go Broadens runtime-only secret key detection to include secrets.entries and secrets.requires declarations.
cmd/wfctl/infra_resolve_state_test.go Adds coverage ensuring required secrets and module-generated secrets are handled correctly at plan time.
cmd/wfctl/infra_plan_env_vars_preserve_test.go Adds a hash-stability regression ensuring required secrets remain templated in user_data.

Comment thread cmd/wfctl/infra.go
Comment thread cmd/wfctl/infra_plan_env_vars_preserve_test.go
@codecov
Copy link
Copy Markdown

codecov Bot commented May 21, 2026

Codecov Report

❌ Patch coverage is 84.28571% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cmd/wfctl/infra.go 86.84% 2 Missing and 3 partials ⚠️
cmd/wfctl/infra_resolve_state.go 85.71% 2 Missing and 2 partials ⚠️
cmd/wfctl/infra_align_rules.go 50.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:276: parsing iteration count: invalid syntax
baseline-bench.txt:331258: parsing iteration count: invalid syntax
baseline-bench.txt:815513: parsing iteration count: invalid syntax
baseline-bench.txt:1135188: parsing iteration count: invalid syntax
baseline-bench.txt:1431901: parsing iteration count: invalid syntax
baseline-bench.txt:1753721: parsing iteration count: invalid syntax
benchmark-results.txt:276: parsing iteration count: invalid syntax
benchmark-results.txt:329383: parsing iteration count: invalid syntax
benchmark-results.txt:637340: parsing iteration count: invalid syntax
benchmark-results.txt:911832: parsing iteration count: invalid syntax
benchmark-results.txt:1211944: parsing iteration count: invalid syntax
benchmark-results.txt:1546280: 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.308m ± 57%   7.396m ± 57%       ~ (p=0.699 n=6)
ComponentLoad-4                     3.657m ±  2%   3.573m ±  1%  -2.29% (p=0.002 n=6)
ComponentExecute-4                  1.936µ ±  1%   1.899µ ±  3%       ~ (p=0.058 n=6)
PoolContention/workers-1-4          1.097µ ±  3%   1.072µ ±  2%  -2.28% (p=0.013 n=6)
PoolContention/workers-2-4          1.099µ ±  2%   1.098µ ±  2%       ~ (p=0.485 n=6)
PoolContention/workers-4-4          1.092µ ±  1%   1.094µ ±  1%       ~ (p=0.792 n=6)
PoolContention/workers-8-4          1.103µ ±  1%   1.081µ ±  1%  -1.95% (p=0.002 n=6)
PoolContention/workers-16-4         1.103µ ±  0%   1.077µ ±  5%       ~ (p=0.065 n=6)
ComponentLifecycle-4                3.711m ±  0%   3.590m ±  0%  -3.26% (p=0.002 n=6)
SourceValidation-4                  2.319µ ±  1%   2.300µ ±  0%  -0.80% (p=0.039 n=6)
RegistryConcurrent-4                818.1n ±  3%   764.1n ±  0%  -6.61% (p=0.002 n=6)
LoaderLoadFromString-4              3.728m ±  2%   3.603m ±  1%  -3.36% (p=0.002 n=6)
geomean                             18.97µ         18.59µ        -1.98%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.974 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=0.461 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.942 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.738 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                  288.8n ± 4%   286.5n ± 4%       ~ (p=0.058 n=6)
CircuitBreakerExecution_Success-4          21.41n ± 0%   21.49n ± 0%  +0.37% (p=0.013 n=6)
CircuitBreakerExecution_Failure-4          66.42n ± 0%   66.36n ± 0%       ~ (p=0.310 n=6)
geomean                                    74.33n        74.21n       -0.17%

                                  │ 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              320.2n ± 27%   322.0n ± 21%       ~ (p=0.240 n=6)
IaCStateBackend_GRPC-4                   9.665m ±  3%   9.556m ±  2%       ~ (p=0.240 n=6)
JQTransform_Simple-4                     691.7n ± 50%   665.7n ± 35%       ~ (p=0.240 n=6)
JQTransform_ObjectConstruction-4         1.667µ ±  1%   1.504µ ±  0%  -9.81% (p=0.002 n=6)
JQTransform_ArraySelect-4                3.674µ ±  1%   3.442µ ±  1%  -6.32% (p=0.002 n=6)
JQTransform_Complex-4                    41.13µ ±  1%   39.30µ ±  1%  -4.46% (p=0.002 n=6)
JQTransform_Throughput-4                 1.999µ ±  1%   1.840µ ±  0%  -7.93% (p=0.002 n=6)
SSEPublishDelivery-4                     64.87n ±  0%   65.08n ±  1%       ~ (p=0.223 n=6)
geomean                                  4.032µ         3.866µ        -4.13%

                                 │ 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.948Mi ± 11%     5.873Mi ± 9%       ~ (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.16%               ²
¹ 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.835k ± 0%     6.835k ± 0%       ~ (p=0.981 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.131µ ± 19%   1.100µ ± 7%       ~ (p=0.240 n=6)
SchemaValidation_AllFields-4                1.661µ ±  3%   1.648µ ± 1%       ~ (p=0.589 n=6)
SchemaValidation_FormatValidation-4         1.612µ ±  3%   1.578µ ± 1%  -2.11% (p=0.011 n=6)
SchemaValidation_ManySchemas-4              1.825µ ±  4%   1.846µ ± 2%       ~ (p=0.667 n=6)
geomean                                     1.533µ         1.516µ       -1.13%

                                    │ 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.277µ ± 11%   1.169µ ± 15%        ~ (p=0.418 n=6)
EventStoreAppend_SQLite-4                  1.316m ±  3%   1.221m ±  4%   -7.20% (p=0.002 n=6)
GetTimeline_InMemory/events-10-4           14.11µ ±  3%   13.23µ ±  1%   -6.23% (p=0.002 n=6)
GetTimeline_InMemory/events-50-4           78.41µ ±  5%   74.57µ ±  3%   -4.89% (p=0.009 n=6)
GetTimeline_InMemory/events-100-4          154.5µ ±  3%   121.6µ ± 19%  -21.32% (p=0.002 n=6)
GetTimeline_InMemory/events-500-4          770.1µ ± 17%   622.6µ ±  0%  -19.15% (p=0.002 n=6)
GetTimeline_InMemory/events-1000-4         1.322m ±  1%   1.276m ±  2%   -3.53% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             117.7µ ±  1%   105.3µ ±  1%  -10.49% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             263.3µ ±  1%   245.1µ ±  0%   -6.92% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            429.5µ ±  1%   414.6µ ±  0%   -3.47% (p=0.002 n=6)
GetTimeline_SQLite/events-500-4            1.840m ±  1%   1.764m ±  0%   -4.12% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.616m ±  2%   3.443m ±  1%   -4.79% (p=0.002 n=6)
geomean                                    233.2µ         213.2µ         -8.57%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  782.5 ± 4%     817.0 ± 6%       ~ (p=0.132 n=6)
EventStoreAppend_SQLite-4                  1.983Ki ± 3%   1.983Ki ± 2%       ~ (p=0.732 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=0.061 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%  -0.00% (p=0.045 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%       ~ (p=0.288 n=6)
geomean                                    67.29Ki        67.53Ki       +0.36%
¹ 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 merged commit b1fa8b3 into main May 21, 2026
24 checks passed
@intel352 intel352 deleted the codex/wfctl-required-secrets-runtime branch May 21, 2026 02:08
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