Skip to content

feat(module): add step.secret_set step#405

Merged
intel352 merged 7 commits into
mainfrom
feat/step-secret-set
Apr 16, 2026
Merged

feat(module): add step.secret_set step#405
intel352 merged 7 commits into
mainfrom
feat/step-secret-set

Conversation

@intel352

Copy link
Copy Markdown
Contributor

Summary

Adds step.secret_set, the write-side mirror of step.secret_fetch. Writes template-resolved values to a named secrets.Provider module.

Motivation

Pipeline-driven setup flows (e.g., OAuth credential storage in zoom-mcp) need to persist user-supplied values to a secrets provider without leaving the YAML pipeline. step.secret_set completes the read/write pair.

Surface

- type: step.secret_set
  name: save-creds
  config:
    module: zoom-secrets
    secrets:
      client_id: "{{ .request.form.client_id }}"
      client_secret: "{{ .request.form.client_secret }}"

Notes

  • Mirrors step.secret_fetch in config loading, module resolution, and error handling.
  • Template values are resolved against the pipeline context before writing.
  • Returns {"set_keys": [...]} in step output for observability.
  • Empty-value writes are allowed (legitimate for clearing a secret); empty key names are rejected.

Test plan

  • Unit tests: write single + multiple keys, verify via provider.Get (12 tests)
  • Error cases: missing module, empty secrets map, wrong service type, nil app, empty key, non-string value, provider error
  • go test ./... passes
  • Schema, documentation, type registry all updated

🤖 Generated with Claude Code

intel352 and others added 2 commits April 16, 2026 09:59
TDD first commit: tests reference NewSecretSetStepFactory and SecretSetProvider
which do not exist yet. Covers happy-path single/multiple key writes, template
resolution from pipeline context, provider errors, missing module, wrong service
type, and nil app.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds step.secret_set, the write-side mirror of step.secret_fetch.
Resolves Go template values against the pipeline context and writes
each key to the named secrets module via provider.Set. Returns
{"set_keys": [...]} for observability. Registers in plugin manifest,
StepFactories, KnownStepTypes, schema registries, golden file, and
DOCUMENTATION.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 16, 2026 14:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 pipeline step type, step.secret_set, intended to persist template-resolved values into a configured secrets provider/module (as a write-side mirror of step.secret_fetch).

Changes:

  • Introduces SecretSetStep implementation + unit tests for config validation, template resolution, and provider write behavior.
  • Registers the new step in the pipelinesteps plugin factory/type lists and wfctl type registry.
  • Updates schemas (step/module + editor golden) and documentation to include step.secret_set.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
schema/testdata/editor-schemas.golden.json Adds editor schema entry for step.secret_set config fields.
schema/step_schema_builtins.go Registers step schema for step.secret_set, including set_keys output.
schema/schema.go Adds step.secret_set to known core module/step types.
schema/module_schema.go Adds module schema entry for step.secret_set (UI/config introspection).
plugins/pipelinesteps/plugin_test.go Extends factory coverage list to include step.secret_set.
plugins/pipelinesteps/plugin.go Registers step.secret_set in plugin metadata and factory map.
module/pipeline_step_secret_set.go Implements the new SecretSetStep and factory.
module/pipeline_step_secret_set_test.go Adds unit tests covering factory validation + execution behavior.
cmd/wfctl/type_registry.go Adds step.secret_set to CLI-known step types.
DOCUMENTATION.md Documents step.secret_set and adds it to the step catalog table.

Comment on lines +88 to +104
setKeys := make([]string, 0, len(s.secrets))

for secretKey, valueTemplate := range s.secrets {
// Resolve the value template against the current pipeline context.
// This enables dynamic values such as form fields from prior steps:
// "{{.steps.form.client_id}}"
resolvedValue, resolveErr := s.tmpl.Resolve(valueTemplate, pc)
if resolveErr != nil {
return nil, fmt.Errorf("secret_set step %q: failed to resolve value for %q: %w", s.name, secretKey, resolveErr)
}

if setErr := provider.Set(ctx, secretKey, resolvedValue); setErr != nil {
return nil, fmt.Errorf("secret_set step %q: failed to set secret %q: %w", s.name, secretKey, setErr)
}

setKeys = append(setKeys, secretKey)
}

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

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

Secrets are written by iterating over a map, so the write order is non-deterministic. If provider.Set fails partway through, which keys were written before the failure will vary run-to-run. Consider sorting the keys first and writing in a deterministic order (you can still return a sorted set_keys output).

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +56
// mockAppWithSetProvider registers a secrets.Provider that supports Set into a MockApplication.
func mockAppWithSetProvider(name string, p SecretSetProvider) *MockApplication {
app := NewMockApplication()
app.Services[name] = p
return app
}

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

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

Tests cover the happy path with a mock SecretSetProvider, but they don't exercise the documented/common case of using the built-in secrets modules (secrets.aws / secrets.vault). Adding a test that registers a real SecretsAWSModule/SecretsVaultModule (or a minimal stub matching how those modules are registered) would catch the current interface mismatch and prevent regressions.

Copilot generated this review using guidance from organization custom instructions.
Comment thread module/pipeline_step_secret_set.go Outdated
Comment on lines +116 to +124
func (s *SecretSetStep) resolveProvider() (SecretSetProvider, error) {
svc, ok := s.app.SvcRegistry()[s.moduleName]
if !ok {
return nil, fmt.Errorf("secret_set step %q: secrets module %q not found in service registry", s.name, s.moduleName)
}
provider, ok := svc.(SecretSetProvider)
if !ok {
return nil, fmt.Errorf("secret_set step %q: service %q does not implement SecretSetProvider (Set method)", s.name, s.moduleName)
}

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

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

resolveProvider requires the service itself to implement SecretSetProvider (Set). The built-in secrets modules (e.g., module/SecretsAWSModule and module/SecretsVaultModule) register themselves as the service instance and currently don't implement Set, so step.secret_set will error with “does not implement SecretSetProvider” when used with those modules. Fix by either adding Set methods on those modules that delegate to their underlying provider, or by updating resolveProvider to detect modules exposing Provider() and use that provider's Set implementation.

Copilot uses AI. Check for mistakes.
@github-actions

github-actions Bot commented Apr 16, 2026

Copy link
Copy Markdown

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:245: parsing iteration count: invalid syntax
baseline-bench.txt:328890: parsing iteration count: invalid syntax
baseline-bench.txt:578245: parsing iteration count: invalid syntax
baseline-bench.txt:859922: parsing iteration count: invalid syntax
baseline-bench.txt:1185447: parsing iteration count: invalid syntax
baseline-bench.txt:1508791: parsing iteration count: invalid syntax
benchmark-results.txt:245: parsing iteration count: invalid syntax
benchmark-results.txt:358497: parsing iteration count: invalid syntax
benchmark-results.txt:641359: parsing iteration count: invalid syntax
benchmark-results.txt:982145: parsing iteration count: invalid syntax
benchmark-results.txt:1263543: parsing iteration count: invalid syntax
benchmark-results.txt:1587355: 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              3.171m ± 228%   3.223m ± 202%       ~ (p=0.818 n=6)
ComponentLoad-4                    3.574m ±  11%   3.579m ±   1%       ~ (p=0.818 n=6)
ComponentExecute-4                 1.917µ ±   1%   1.950µ ±   1%  +1.70% (p=0.004 n=6)
PoolContention/workers-1-4         1.081µ ±   1%   1.085µ ±   1%       ~ (p=0.368 n=6)
PoolContention/workers-2-4         1.074µ ±   2%   1.094µ ±   2%  +1.86% (p=0.026 n=6)
PoolContention/workers-4-4         1.086µ ±   2%   1.101µ ±   1%       ~ (p=0.056 n=6)
PoolContention/workers-8-4         1.097µ ±   1%   1.099µ ±   1%       ~ (p=0.673 n=6)
PoolContention/workers-16-4        1.098µ ±   1%   1.105µ ±   1%       ~ (p=0.331 n=6)
ComponentLifecycle-4               3.608m ±   1%   3.639m ±   1%  +0.84% (p=0.009 n=6)
SourceValidation-4                 2.240µ ±   1%   2.272µ ±   1%  +1.38% (p=0.004 n=6)
RegistryConcurrent-4               773.4n ±   5%   798.6n ±   3%       ~ (p=0.240 n=6)
LoaderLoadFromString-4             3.591m ±   1%   3.650m ±   2%  +1.65% (p=0.002 n=6)
geomean                            17.34µ          17.56µ         +1.26%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.255 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.738 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.853 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                  284.3n ± 3%   287.2n ± 6%       ~ (p=0.177 n=6)
CircuitBreakerExecution_Success-4          21.53n ± 0%   21.55n ± 0%       ~ (p=0.097 n=6)
CircuitBreakerExecution_Failure-4          65.99n ± 0%   65.84n ± 1%       ~ (p=0.065 n=6)
geomean                                    73.92n        74.14n       +0.30%

                                  │ 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              │
JQTransform_Simple-4                     999.2n ± 13%   884.4n ± 28%       ~ (p=0.485 n=6)
JQTransform_ObjectConstruction-4         1.450µ ±  2%   1.435µ ±  4%       ~ (p=0.366 n=6)
JQTransform_ArraySelect-4                3.331µ ±  1%   3.320µ ±  5%       ~ (p=0.937 n=6)
JQTransform_Complex-4                    38.24µ ±  2%   38.05µ ±  2%       ~ (p=0.240 n=6)
JQTransform_Throughput-4                 1.783µ ±  0%   1.768µ ±  1%       ~ (p=0.056 n=6)
SSEPublishDelivery-4                     73.88n ±  1%   71.47n ±  0%  -3.25% (p=0.002 n=6)
geomean                                  1.702µ         1.651µ        -2.99%

                                 │ baseline-bench.txt │        benchmark-results.txt         │
                                 │        B/op        │     B/op      vs base                │
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.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │     allocs/op      │ allocs/op   vs base                │
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.116µ ± 5%   1.105µ ± 3%       ~ (p=0.158 n=6)
SchemaValidation_AllFields-4                 1.670µ ± 3%   1.666µ ± 7%       ~ (p=0.784 n=6)
SchemaValidation_FormatValidation-4          1.592µ ± 3%   1.598µ ± 1%       ~ (p=1.000 n=6)
SchemaValidation_ManySchemas-4               1.814µ ± 4%   1.811µ ± 3%       ~ (p=0.589 n=6)
geomean                                      1.523µ        1.519µ       -0.26%

                                    │ 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.165µ ±  8%   1.185µ ± 16%        ~ (p=1.000 n=6)
EventStoreAppend_SQLite-4                  1.752m ± 19%   1.443m ±  2%  -17.63% (p=0.002 n=6)
GetTimeline_InMemory/events-10-4           13.25µ ±  3%   13.34µ ±  2%        ~ (p=0.818 n=6)
GetTimeline_InMemory/events-50-4           74.50µ ± 18%   74.76µ ±  4%        ~ (p=0.937 n=6)
GetTimeline_InMemory/events-100-4          122.2µ ±  1%   130.8µ ± 16%        ~ (p=0.394 n=6)
GetTimeline_InMemory/events-500-4          629.1µ ±  0%   623.3µ ±  1%        ~ (p=0.065 n=6)
GetTimeline_InMemory/events-1000-4         1.289m ±  2%   1.274m ±  1%   -1.22% (p=0.004 n=6)
GetTimeline_SQLite/events-10-4             106.7µ ±  1%   108.9µ ±  1%   +2.14% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             243.1µ ±  0%   248.9µ ±  1%   +2.40% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            411.3µ ±  0%   419.4µ ±  1%   +1.98% (p=0.002 n=6)
GetTimeline_SQLite/events-500-4            1.746m ±  1%   1.768m ±  0%   +1.27% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.410m ±  3%   3.513m ±  2%   +3.03% (p=0.009 n=6)
geomean                                    219.7µ         219.5µ         -0.11%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  799.0 ± 4%     786.5 ± 9%       ~ (p=0.394 n=6)
EventStoreAppend_SQLite-4                  1.988Ki ± 3%   1.984Ki ± 2%       ~ (p=0.942 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.545 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ± 0%       ~ (p=0.076 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.076 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%       ~ (p=0.455 n=6)
geomean                                    67.42Ki        67.32Ki       -0.15%
¹ 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 and others added 2 commits April 16, 2026 10:27
- Document partial-failure behavior in Execute godoc (earlier writes
  committed, no rollback — secrets backends lack transactions)
- Document that empty resolved values are permitted (clearing a secret)
- Add TestSecretSetStep_WhitespaceOnlyKey (factory validation)
- Add TestSecretSetStep_PartialFailure (multi-key with mid-write error)
- Clarify mock provider comment re: broader interface for test verification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… write order

Address Copilot review feedback on PR #405:

- resolveProvider now falls through to Provider() accessor when the
  service doesn't implement SecretSetProvider directly. This matches how
  SecretsAWSModule/SecretsVaultModule/SecretsKeychainModule work: they
  expose Get on the wrapper but Set only on the underlying Provider().
- Sort keys before iterating for writes, so partial-failure behavior is
  deterministic regardless of map iteration order.
- Add TestSecretSetStep_ProviderAccessorFallback exercising the indirect
  resolution path.
- Fix misleading comment claiming modules implement Set directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 16, 2026 14:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 10 out of 10 changed files in this pull request and generated 3 comments.

Comment thread module/pipeline_step_secret_set.go Outdated
Comment thread module/pipeline_step_secret_set.go Outdated
Comment thread module/pipeline_step_secret_set.go Outdated
intel352 and others added 2 commits April 16, 2026 10:58
- Remove premature SecretsKeychainModule references from comments
  (secrets.keychain is in a separate PR)
- Remove redundant sort.Strings(setKeys) — keys already built in
  sorted order from sortedKeys iteration
- Add specific error when Provider() accessor returns nil, distinguishing
  uninitialized module from wrong service type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CodeQL flagged the variable `secretKey` as sensitive data flowing to
log calls. The variable holds the key *name* (e.g. "client_id"), not
the secret value. Renaming to `keyName` avoids the heuristic match
while preserving clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 16, 2026 15:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 10 out of 10 changed files in this pull request and generated 1 comment.

Comment on lines +109 to +120
valueTemplate := s.secrets[keyName]
// Resolve the value template against the current pipeline context.
// This enables dynamic values such as form fields from prior steps:
// "{{.steps.form.client_id}}"
resolvedValue, resolveErr := s.tmpl.Resolve(valueTemplate, pc)
if resolveErr != nil {
return nil, fmt.Errorf("secret_set step %q: failed to resolve value for %q: %w", s.name, keyName, resolveErr)
}

if setErr := provider.Set(ctx, keyName, resolvedValue); setErr != nil {
return nil, fmt.Errorf("secret_set step %q: failed to set secret %q: %w", s.name, keyName, setErr)
}

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

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

In non-strict template mode, a missing map key resolves to the literal string "" (see pipeline.TemplateEngine behavior). Because this step permits empty values and does not validate the resolved string, a typo in a template can silently write "" (or a string containing it) into the secrets backend. Consider failing fast when the resolved value is "" (or contains it) for templated inputs, or resolving with a strict/"missingkey=error" pass before writing to prevent accidental secret corruption.

Copilot uses AI. Check for mistakes.
In non-strict template mode, a missing map key resolves to the literal
string "<no value>" rather than returning an error. For display steps
this is acceptable, but for secret_set it would silently corrupt the
secrets backend. Fail fast when the resolved value contains the
sentinel, directing users to check for typos in template keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@intel352 intel352 merged commit f7611fb into main Apr 16, 2026
18 checks passed
@intel352 intel352 deleted the feat/step-secret-set branch April 16, 2026 16:18
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