Skip to content

template: warn on missing key access; add strict_templates opt-in#372

Merged
intel352 merged 5 commits intomainfrom
copilot/fix-template-missingkey-issue
Mar 24, 2026
Merged

template: warn on missing key access; add strict_templates opt-in#372
intel352 merged 5 commits intomainfrom
copilot/fix-template-missingkey-issue

Conversation

Copy link
Contributor

Copilot AI commented Mar 24, 2026

missingkey=zero silently resolved typos in step field references (e.g. .steps.auth.affilate_id) to empty string, producing invisibly wrong data with no error or log signal at any stage.

Changes

Option C — runtime warning (default, backward-compatible)

  • TemplateEngine.Resolve() now attempts execution with missingkey=error first; on a missing-key hit, logs a WARN via the pipeline's logger (falling back to slog.Default()) then retries with missingkey=zero
  • Warning log emits pipeline name (from pc.Metadata["pipeline"]) instead of the full template string to avoid leaking secrets/PII into logs

Option A — strict mode (opt-in per pipeline)

  • New strict_templates: true pipeline config field; when set, missing key access immediately fails the step instead of warning+continuing
  • Strict mode applies to both direct dot-access ({{ .steps.auth.field }}) and the step/trigger helper functions ({{ step "auth" "field" }})
pipelines:
  my-pipeline:
    strict_templates: true   # missing key → hard error
    steps:
      - name: process
        type: step.set
        config:
          values:
            tenant: "{{ .steps.auth.affilate_id }}"   # typo → step fails immediately

Without strict_templates, the same typo produces:

WARN template resolved missing key to zero value pipeline=my-pipeline error="..."

Key files

  • interfaces/pipeline.goPipelineContext gains StrictTemplates bool + Logger *slog.Logger
  • config/pipeline.goPipelineConfig gains strict_templates field
  • module/pipeline_executor.goPipeline gains StrictTemplates; propagates both to context in Execute()
  • module/pipeline_template.go — rewrites Resolve() with two-pass detection + isMissingKeyError() helper; step/trigger helpers return (any, error) and propagate errors in strict mode
  • engine.go — reads pipeCfg.StrictTemplates when constructing pipelines
  • DOCUMENTATION.md — "Missing Key Behaviour" section added under template docs
Original prompt

This section details on the original issue you should resolve

<issue_title>Template missingkey=zero silently swallows field-level typos in step references</issue_title>
<issue_description>## Summary

The pipeline template engine uses Option("missingkey=zero") (Go text/template), which means any typo in a field-level reference silently resolves to an empty/zero value at runtime. There is no compile-time, load-time, or static-analysis check that catches these errors.

Combined with the fact that all inter-step data flows through map[string]any (no Go structs), this creates a class of bugs that are invisible until runtime — and even then may not produce obvious errors, just silently wrong data.

Current behavior

steps:
  - name: auth
    type: step.auth_validate

  - name: process
    type: step.set
    config:
      values:
        # Correct: resolves to the actual affiliate_id
        tenant: "{{ .steps.auth.affiliate_id }}"
        # Typo: silently resolves to "" — no error at any stage
        role: "{{ .steps.auth.affilate_id }}"
  • role silently becomes "" at runtime
  • No error in pipeline execution (pipeline "succeeds")
  • Downstream steps receive empty string instead of failing fast
  • wfctl template validate catches step name typos (steps.nonexistent_step) but NOT field-level typos (steps.auth.misspelled_field)

Root cause

  1. PipelineContext stores all data as map[string]map[string]any (interfaces/pipeline.go:51-52) — no struct fields to catch at compile time
  2. TemplateEngine.Resolve() uses .Option("missingkey=zero") (module/pipeline_template.go:355) — Go's text/template returns the zero value for missing map keys instead of erroring
  3. wfctl template validate regex \.steps\.([a-zA-Z_][a-zA-Z0-9_-]*) only extracts the step name portion, completely ignoring the field path after it (cmd/wfctl/template_validate.go:569)

Impact

This affects every pipeline that references step outputs. Common failure modes:

  • Typo in field name: steps.auth.affilate_id → empty string, pipeline proceeds with wrong data
  • Rename a step output field: Downstream steps silently get empty instead of failing
  • Nested object access: steps.query.row.column_name — no validation that row contains column_name
  • Conditional guards: if: "{{ .steps.decode.valid }}" — a typo here means the guard always evaluates one way

Proposed solutions

Option A: missingkey=error mode (strict templates)

Add an opt-in strict mode that switches to Option("missingkey=error"):

pipelines:
  my-pipeline:
    strict_templates: true  # or a global engine setting

This would cause text/template to return an error instead of zero when a key is missing. Breaking change if enabled globally, but could be opt-in per pipeline or via a wfctl flag.

Option B: Enhanced static validation in wfctl template validate

Extend validateStepRef() to infer expected output schemas from step types:

  • step.db_query with mode: single → output has row, found
  • step.db_query with mode: list → output has rows, count
  • step.auth_validate → output has auth_user_id, affiliate_id
  • step.request_parse → output has headers, body, path_params, query_params
  • step.base64_decode → output has valid, data, mime_type, etc.

Then validate that field-level references in templates match the known output schema of the referenced step.

Option C: Runtime warning mode

Log a warning (not error) when a template resolves a missing key to zero value. This preserves backward compatibility while making silent failures visible:

WARN template resolved missing key: steps.auth.affilate_id → "" (pipeline: my-pipeline, step: process)

References

  • Template resolution: module/pipeline_template.go:348-366 (missingkey=zero on line 355)
  • Pipeline context: interfaces/pipeline.go:46-60 (map[string]any types)
  • Step output merging: interfaces/pipeline.go:87-98
  • Template validation: cmd/wfctl/template_validate.go:568-646 (step name only)
  • Test proving silent zero: module/pipeline_template_test.go:93-104 (TestTemplateEngine_MissingKeyReturnsZeroValue)</issue_description>

Comments on the Issue (you are @copilot in this section)


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Copilot AI changed the title [WIP] Fix template missingkey=zero issue with field-level typos template: warn on missing key access; add strict_templates opt-in Mar 24, 2026
Copilot AI requested a review from intel352 March 24, 2026 16:45
@intel352 intel352 marked this pull request as ready for review March 24, 2026 17:17
Copilot AI review requested due to automatic review settings March 24, 2026 17:17
Copy link
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 safer template evaluation for pipelines by surfacing missing-key typos at runtime (WARN by default) and allowing opt-in strict mode (strict_templates: true) to fail fast, improving correctness of config-driven workflows.

Changes:

  • Implement two-pass template execution: detect missing keys with missingkey=error, then warn and fall back to missingkey=zero (or hard error in strict mode).
  • Add strict_templates pipeline config and propagate it through engine → pipeline → pipeline context.
  • Update tests and documentation; includes a number of gofmt/alignment-only changes across unrelated files.

Reviewed changes

Copilot reviewed 7 out of 29 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
schema/step_schema_builtins.go Formatting-only alignment in built-in step schema registration.
schema/coercion.go Formatting-only alignment in type coercion registry map.
plugin/autofetch_test.go Formatting-only alignment in test table fields/comments.
module/step_output_redactor_test.go Formatting-only alignment in test table struct fields.
module/pipeline_template_test.go Add tests for missing-key WARN logging and strict mode error behavior.
module/pipeline_template.go Implement strict/non-strict missing-key handling with warning + fallback execution.
module/pipeline_step_deploy_rolling_test.go Formatting-only alignment in test config map.
module/pipeline_step_deploy_rolling.go Formatting-only alignment in HTTPDeployDriver struct fields.
module/pipeline_step_deploy_rollback_test.go Formatting-only alignment in test config map.
module/pipeline_step_deploy_rollback.go Formatting-only alignment in structs/output map.
module/pipeline_step_deploy_canary_test.go Formatting-only alignment in mock driver struct fields.
module/pipeline_step_deploy_blue_green_test.go Formatting-only alignment in mock driver struct fields.
module/pipeline_step_container_build_test.go Formatting-only alignment in mock struct fields.
module/pipeline_step_ai_extract_test.go Formatting-only alignment in test config map.
module/pipeline_step_ai_classify_test.go Formatting-only alignment in test config map.
module/pipeline_executor.go Add StrictTemplates to Pipeline and propagate StrictTemplates/logger into PipelineContext.
module/infra_module_test.go Formatting-only alignment in mock app methods and test config map.
module/infra_module_deploy_bridge_test.go Formatting-only alignment in mock methods/fields.
module/iac_state_gcs_test.go Formatting-only alignment in mock struct fields/comments.
module/iac_state_azure_test.go Formatting-only alignment in mock struct fields/comments.
interfaces/pipeline.go Add StrictTemplates and Logger fields to PipelineContext.
interfaces/iac_test.go Formatting-only alignment in mock methods.
engine.go Wire pipeCfg.StrictTemplates into constructed pipelines.
config/pipeline.go Add strict_templates field to PipelineConfig.
cmd/wfctl/test.go Formatting-only alignment in CLI test struct fields.
cmd/wfctl/plugin_install_new_test.go Formatting-only alignment in test tarball entry maps.
cmd/wfctl/main.go Formatting-only alignment in commands map.
cmd/wfctl/infra_state.go Formatting-only alignment in structs used for state import/export.
DOCUMENTATION.md Document missing-key behavior, WARN default, and strict opt-in.

Comment on lines +440 to +459
To turn the warning into a hard error, set `strict_templates: true` on the pipeline:

```yaml
pipelines:
my-pipeline:
strict_templates: true # any missing key access fails the pipeline step
steps:
- name: process
type: step.set
config:
values:
tenant: "{{ .steps.auth.affilate_id }}" # typo: affilate_id instead of affiliate_id → step fails immediately
```

| Mode | `strict_templates` | Missing key result |
|------|-------------------|--------------------|
| Default | `false` | Zero value (`<no value>`) + WARN log |
| Strict | `true` | Step returns an error |

`wfctl template validate --config workflow.yaml` lints template expressions and warns on undefined step references and forward references. Use `strict_templates: true` in the pipeline config to catch field-level typos at runtime.
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The docs imply strict_templates: true turns missing-key access into a hard error generally, but as implemented the step/trigger template helper functions still return nil on missing keys (so strict mode won't catch typos when using the documented "Preferred syntax"). Either adjust the implementation to make those helpers error in strict mode, or update this section to spell out the current limitation so users aren't misled.

Copilot uses AI. Check for mistakes.
Comment on lines +398 to +404
// Log a warning about the missing key so developers can spot typos.
logger := slog.Default()
if pc != nil && pc.Logger != nil {
logger = pc.Logger
}
logger.Warn("template resolved missing key to zero value",
"template", tmplStr,
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The WARN log includes the full (preprocessed) template string. Pipeline templates can contain literals (including secrets/PII) and logging them on missing-key access can leak sensitive data into logs. Consider omitting the full template text (or truncating/sanitizing it) and instead logging safer context (e.g., pipeline name from pc.Metadata["pipeline"], plus the missing-key error) so the warning is still actionable without exposing configuration contents.

Suggested change
// Log a warning about the missing key so developers can spot typos.
logger := slog.Default()
if pc != nil && pc.Logger != nil {
logger = pc.Logger
}
logger.Warn("template resolved missing key to zero value",
"template", tmplStr,
// Log a warning about the missing key so developers can spot typos,
// without including the full template text (which may contain secrets).
logger := slog.Default()
if pc != nil && pc.Logger != nil {
logger = pc.Logger
}
pipelineName := "<unknown>"
if pc != nil && pc.Metadata != nil {
if v, ok := pc.Metadata["pipeline"]; ok {
pipelineName = fmt.Sprint(v)
}
}
logger.Warn("template resolved missing key to zero value",
"pipeline", pipelineName,

Copilot uses AI. Check for mistakes.
Comment on lines +358 to +364
// Missing key behaviour:
// - When pc.StrictTemplates is true (Option A), any reference to a missing
// map key causes an immediate error, surfacing typos as failures.
// - When pc.StrictTemplates is false (the default, Option C), a missing key
// resolves to the zero value AND a WARN log is emitted via pc.Logger (or
// slog.Default() when no logger is set) so that the silent failure is
// visible without breaking existing pipelines.
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

Strict mode currently only affects Go template map key access via missingkey=error. The context helper functions step and trigger still return nil on missing keys, so strict_templates: true will not fail for typos when templates use these helpers (which are documented as the preferred syntax for hyphenated step names). Either update step/trigger to return an error in strict mode (template funcs can return (any, error)) and add tests for that behavior, or clarify in the docs/comments that strict mode does not apply to these helpers.

Suggested change
// Missing key behaviour:
// - When pc.StrictTemplates is true (Option A), any reference to a missing
// map key causes an immediate error, surfacing typos as failures.
// - When pc.StrictTemplates is false (the default, Option C), a missing key
// resolves to the zero value AND a WARN log is emitted via pc.Logger (or
// slog.Default() when no logger is set) so that the silent failure is
// visible without breaking existing pipelines.
// Missing key behaviour (direct map access):
// - When pc.StrictTemplates is true (Option A), any reference to a missing
// map key (e.g. .Steps.foo.bar) causes an immediate error via
// missingkey=error, surfacing typos as failures.
// - When pc.StrictTemplates is false (the default, Option C), a missing key
// resolves to the zero value AND a WARN log is emitted via pc.Logger (or
// slog.Default() when no logger is set) so that the silent failure is
// visible without breaking existing pipelines.
//
// NOTE: Strict template mode only affects direct map key resolution driven by
// missingkey=error. It does NOT change the behaviour of helper functions such
// as step or trigger, which may still return nil for unknown keys; typos when
// using those helpers will therefore not cause Resolve to fail even when
// pc.StrictTemplates is true.

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

github-actions bot commented Mar 24, 2026

⏱ 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:310794: parsing iteration count: invalid syntax
baseline-bench.txt:588313: parsing iteration count: invalid syntax
baseline-bench.txt:892634: parsing iteration count: invalid syntax
baseline-bench.txt:1193526: parsing iteration count: invalid syntax
baseline-bench.txt:1503592: parsing iteration count: invalid syntax
benchmark-results.txt:245: parsing iteration count: invalid syntax
benchmark-results.txt:317628: parsing iteration count: invalid syntax
benchmark-results.txt:597025: parsing iteration count: invalid syntax
benchmark-results.txt:892673: parsing iteration count: invalid syntax
benchmark-results.txt:1167243: parsing iteration count: invalid syntax
benchmark-results.txt:1450378: 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.105m ± 212%   3.310m ± 206%       ~ (p=0.132 n=6)
ComponentLoad-4                    3.576m ±   5%   3.608m ±   6%       ~ (p=0.240 n=6)
ComponentExecute-4                 1.946µ ±   0%   1.939µ ±   1%       ~ (p=0.426 n=6)
PoolContention/workers-1-4         1.087µ ±   3%   1.091µ ±   1%       ~ (p=0.784 n=6)
PoolContention/workers-2-4         1.085µ ±   4%   1.073µ ±   5%       ~ (p=0.180 n=6)
PoolContention/workers-4-4         1.084µ ±   1%   1.083µ ±   1%       ~ (p=0.968 n=6)
PoolContention/workers-8-4         1.084µ ±   2%   1.087µ ±   1%       ~ (p=1.000 n=6)
PoolContention/workers-16-4        1.089µ ±   1%   1.095µ ±   2%       ~ (p=0.071 n=6)
ComponentLifecycle-4               3.604m ±   1%   3.686m ±   2%  +2.26% (p=0.026 n=6)
SourceValidation-4                 2.233µ ±   1%   2.233µ ±   2%       ~ (p=0.781 n=6)
RegistryConcurrent-4               766.4n ±   3%   807.8n ±   3%  +5.40% (p=0.009 n=6)
LoaderLoadFromString-4             3.619m ±   1%   3.747m ±   3%  +3.54% (p=0.002 n=6)
geomean                            17.32µ          17.58µ         +1.51%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.719 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=0.190 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.457 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.978 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                  286.1n ± 6%   287.2n ± 1%       ~ (p=0.558 n=6)
CircuitBreakerExecution_Success-4          22.50n ± 0%   22.50n ± 1%       ~ (p=0.286 n=6)
CircuitBreakerExecution_Failure-4          64.27n ± 0%   64.76n ± 1%  +0.77% (p=0.009 n=6)
geomean                                    74.51n        74.80n       +0.39%

                                  │ 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                     852.2n ± 28%   844.1n ± 30%       ~ (p=0.240 n=6)
JQTransform_ObjectConstruction-4         1.408µ ±  0%   1.403µ ±  0%  -0.39% (p=0.019 n=6)
JQTransform_ArraySelect-4                3.255µ ±  0%   3.269µ ±  1%       ~ (p=0.106 n=6)
JQTransform_Complex-4                    38.33µ ±  1%   37.53µ ±  1%  -2.09% (p=0.002 n=6)
JQTransform_Throughput-4                 1.733µ ±  0%   1.735µ ±  1%       ~ (p=0.236 n=6)
SSEPublishDelivery-4                     71.99n ±  0%   70.07n ±  0%  -2.67% (p=0.002 n=6)
geomean                                  1.629µ         1.614µ        -0.93%

                                 │ 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.084µ ± 10%   1.087µ ± 6%       ~ (p=0.937 n=6)
SchemaValidation_AllFields-4                1.646µ ±  1%   1.695µ ± 4%  +2.95% (p=0.004 n=6)
SchemaValidation_FormatValidation-4         1.585µ ±  6%   1.571µ ± 3%       ~ (p=0.894 n=6)
SchemaValidation_ManySchemas-4              1.802µ ±  3%   1.815µ ± 3%       ~ (p=0.665 n=6)
geomean                                     1.502µ         1.514µ       +0.76%

                                    │ 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.312µ ± 16%   1.486µ ± 18%        ~ (p=0.310 n=6)
EventStoreAppend_SQLite-4                  1.732m ±  4%   1.560m ± 37%        ~ (p=0.180 n=6)
GetTimeline_InMemory/events-10-4           13.93µ ±  4%   14.60µ ±  4%   +4.81% (p=0.026 n=6)
GetTimeline_InMemory/events-50-4           74.05µ ± 17%   82.62µ ± 22%  +11.57% (p=0.026 n=6)
GetTimeline_InMemory/events-100-4          124.5µ ±  1%   129.0µ ±  1%   +3.62% (p=0.002 n=6)
GetTimeline_InMemory/events-500-4          640.5µ ±  1%   662.9µ ±  1%   +3.51% (p=0.002 n=6)
GetTimeline_InMemory/events-1000-4         1.327m ±  2%   1.372m ±  1%   +3.35% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             111.7µ ±  1%   116.1µ ±  1%   +3.85% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             256.5µ ±  1%   271.1µ ±  2%   +5.72% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            432.9µ ±  2%   443.4µ ±  1%   +2.44% (p=0.015 n=6)
GetTimeline_SQLite/events-500-4            1.825m ±  1%   1.872m ±  1%   +2.57% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.538m ±  2%   3.638m ±  2%   +2.82% (p=0.009 n=6)
geomean                                    228.1µ         236.9µ         +3.82%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  833.0 ± 8%     804.5 ± 6%       ~ (p=0.699 n=6)
EventStoreAppend_SQLite-4                  1.984Ki ± 2%   1.983Ki ± 1%       ~ (p=0.905 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=0.232 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.015 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%  -0.00% (p=0.028 n=6)
geomean                                    67.64Ki        67.44Ki       -0.29%
¹ 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
Copy link
Contributor

@copilot apply changes based on the comments in this thread

Copy link
Contributor Author

Copilot AI commented Mar 24, 2026

@copilot apply changes based on the comments in this thread

Applied all three review changes in d42e671:

  1. Security (log PII): Replaced "template", tmplStr with "pipeline", pipelineName in the WARN log — template contents no longer appear in logs. Pipeline name is extracted from pc.Metadata["pipeline"].

  2. step/trigger helpers in strict mode: Updated both helpers to return (any, error). In strict mode they now error when a step is missing, a key is not found, or an intermediate value is not a map. Default mode still returns nil (backward-compatible).

  3. Docs/comments: Updated Resolve() doc comment and DOCUMENTATION.md to accurately state that strict mode covers both direct dot-access and step/trigger helper calls. Also updated the log format example (no longer shows template=).

@intel352 intel352 merged commit 0244edb into main Mar 24, 2026
18 checks passed
@intel352 intel352 deleted the copilot/fix-template-missingkey-issue branch March 24, 2026 21:35
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.

Template missingkey=zero silently swallows field-level typos in step references

3 participants