Skip to content

wfctl validate: add field-level cross-reference checking for step outputs, SQL aliases, and plain-string step refs#373

Merged
intel352 merged 5 commits intomainfrom
copilot/add-cross-reference-checking-validation
Mar 24, 2026
Merged

wfctl validate: add field-level cross-reference checking for step outputs, SQL aliases, and plain-string step refs#373
intel352 merged 5 commits intomainfrom
copilot/add-cross-reference-checking-validation

Conversation

Copy link
Contributor

Copilot AI commented Mar 24, 2026

wfctl validate and wfctl template validate caught structural errors (unknown types, missing steps) but not field-level reference errors — a renamed SQL alias or wrong output field only failed at runtime.

What's added

  • Step output field validation — when a template accesses .steps.query.someField, the first field segment is now checked against the step type's known outputs via schema.InferStepOutputs. Unknown fields emit a warning with the list of valid outputs.

  • SQL alias extraction (best-effort) — for step.db_query / step.db_query_cached refs with a row.<col> path, the SQL SELECT clause is parsed to extract column aliases. A mismatch (e.g. template reads row.token but query selects auth_token) emits a warning. Silent on SELECT * and dynamic SQL.

  • Plain-string step ref validation — config fields that accept bare context-key paths (secret_from, backend_url_key, field in conditional/branch steps) are now validated through the same validateStepRef logic, catching nonexistent steps and unknown output fields without a template expression.

Example

- name: load_integration
  type: step.db_query
  config:
    query: "SELECT auth_token FROM integrations WHERE id = $1"
    mode: single

- name: verify
  type: step.webhook_verify
  config:
    secret_from: steps.load_integration.row.token  # ← 'token' not in SELECT → warning
pipeline "webhook" step "verify": references step "load_integration" output field
"row.token" but the SQL query does not select column "token" (available: auth_token)

Implementation notes

  • stepRefDotRe extended with a second capture group for the field-path suffix (.row.auth_token)
  • stepBuildInfo struct added to carry per-step type + config through validatePipelineTemplates
  • validateStepRef signature extended; validatePlainStepRefs added as a new helper
  • Reuses the existing extractSQLColumns function already present in api_extract.go
Original prompt

This section details on the original issue you should resolve

<issue_title>wfctl validate: no cross-reference checking for DB columns, step output fields, or imported configs</issue_title>
<issue_description>## Summary

wfctl validate and wfctl template validate check structural correctness (valid YAML, known module/step/trigger types, step name existence) but do not validate field-level references across the data flow. When a DB column is renamed, a step output field changes, or a cross-file import breaks, there is no static check that catches it — the error only surfaces at runtime.

What is validated today

Check Status
Valid YAML syntax
Known module types
Known step types
Known trigger types
Step name references (steps.X exists)
Forward references (step references later step)
Self-references (step references itself)

What is NOT validated

Check Status Example
Step output field names steps.query.row.nonexistent_column
DB column alignment SQL returns auth_token but template reads token
Step type output schema step.db_query mode=single produces {row, found} but not checked
Cross-file import targets Imported YAML may reference pipelines/steps that don't exist
Conditional field paths field: "steps.query.found" in step.conditional — found not validated
secret_from paths secret_from: steps.load_integration.row.auth_token — not validated
backend_url_key paths backend_url_key: "row.backend_url" — not validated

Real-world example

From a production pipeline (messaging.yaml):

- name: load_integration
  type: step.db_query
  config:
    query: >
      SELECT provider_config->>'auth_token' AS auth_token, affiliate_id
      FROM messaging_integrations WHERE id = $1
    mode: single

- name: verify
  type: step.webhook_verify
  config:
    secret_from: steps.load_integration.row.auth_token  # references SQL alias

- name: upsert_texter
  type: step.db_exec
  config:
    query: >
      INSERT INTO messaging_texters_{{.steps.load_integration.row.affiliate_id}}

If someone renames the SQL alias auth_tokentoken or affiliate_idtenant_id:

  • wfctl validate passes ✅
  • wfctl template validate passes ✅ (only checks that load_integration step exists)
  • Pipeline fails at runtime with empty/wrong values

Proposed approach

1. Step type output schema registry

Each built-in step type could declare its output schema:

// step.db_query output schema depends on mode
func (s *DBQueryStep) OutputSchema(config map[string]any) map[string]FieldType {
    mode := config["mode"]
    switch mode {
    case "single":
        return map[string]FieldType{"row": Object, "found": Bool}
    case "list":
        return map[string]FieldType{"rows": Array, "count": Int}
    }
}

2. Deep template reference validation

Extend validateStepRef to walk the full dot-path:

  • steps.auth → check step exists ✅ (already done)
  • steps.auth.affiliate_id → check auth step type's output schema for affiliate_id
  • steps.query.row.column_name → check query step type's output schema for row, warn that column_name cannot be statically verified (depends on SQL)

3. SQL alias extraction (best-effort)

For step.db_query, parse the SQL SELECT clause to extract column aliases, then validate references against them. This would be best-effort (can't handle SELECT * or dynamic SQL) but would catch the most common case.

4. Cross-file import validation

When processing imports:, verify that all step names and pipeline names referenced in templates actually exist in the merged config.

References

  • Current validation: cmd/wfctl/validate.go:117-150
  • Template validation: cmd/wfctl/template_validate.go:568-646 (step name only)
  • Step ref regex: cmd/wfctl/template_validate.go:569 — stops at step name, ignores field path
  • Pipeline context types: interfaces/pipeline.go:46-60map[string]any throughout
  • Related: Template missingkey=zero silently swallows field-level typos in step references #367 (missingkey=zero silently swallows typos at runtime)</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.

…s, and plain-string step refs

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoCodeAlone/workflow/sessions/70dc13a0-5cff-4fcf-bf94-6417d19991cf
Copilot AI changed the title [WIP] Add cross-reference checking for DB columns and step outputs wfctl validate: add field-level cross-reference checking for step outputs, SQL aliases, and plain-string step refs Mar 24, 2026
Copilot AI requested a review from intel352 March 24, 2026 16:40
@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

This PR enhances wfctl validate / wfctl template validate by adding static, field-level cross-reference checks for step-output paths found in templates and selected plain-string config fields, with additional best-effort SQL SELECT alias validation for step.db_query-style steps. This reduces runtime-only failures due to typos or renamed fields/aliases.

Changes:

  • Extend template step-reference parsing to capture and validate the first output field segment (via schema.StepSchemaRegistry.InferStepOutputs).
  • Add best-effort SQL column/alias extraction validation for steps.<db_query>.row.<col> references.
  • Validate certain plain-string config fields (secret_from, backend_url_key, field) using the same step-ref validation logic, and add targeted tests.

Reviewed changes

Copilot reviewed 2 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
cmd/wfctl/template_validate.go Adds field-suffix capture to step ref parsing and validates output keys + SQL aliases; introduces plain-string step path validation.
cmd/wfctl/template_validate_test.go Adds test coverage for output-key validation, SQL alias checking, and plain-string step refs.
cmd/wfctl/test.go Formatting-only (gofmt alignment).
cmd/wfctl/plugin_install_new_test.go Formatting-only (gofmt alignment).
cmd/wfctl/main.go Formatting-only (gofmt alignment).
cmd/wfctl/infra_state.go Formatting-only (gofmt alignment).

Comment on lines +760 to +764
if matchedOutput == nil {
result.Warnings = append(result.Warnings,
fmt.Sprintf("pipeline %q step %q: references step %q output field %q which is not a known output of step type %q (known outputs: %s)",
pipelineName, currentStep, refName, firstField, info.stepType, joinOutputKeys(outputs)))
return
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 output-field validation will incorrectly warn for steps whose outputs are intentionally dynamic map keys (e.g. step.secret_fetch exposes arbitrary keys from its secrets map and its schema uses the placeholder output key "(key)"). With the current exact-match check on outputs[i].Key == firstField, any access like .steps.fetch.api_key will be flagged as unknown even though it’s valid. Consider treating placeholder keys like "(key)" as a wildcard (skip validation for that step type) or enhancing output inference for those steps to return the concrete keys from config so validation can be accurate.

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:285129: parsing iteration count: invalid syntax
benchmark-results.txt:591076: parsing iteration count: invalid syntax
benchmark-results.txt:873104: parsing iteration count: invalid syntax
benchmark-results.txt:1133712: parsing iteration count: invalid syntax
benchmark-results.txt:1433287: 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.222m ± 153%       ~ (p=0.310 n=6)
ComponentLoad-4                    3.576m ±   5%   3.687m ±   7%       ~ (p=0.093 n=6)
ComponentExecute-4                 1.946µ ±   0%   1.954µ ±   3%  +0.39% (p=0.030 n=6)
PoolContention/workers-1-4         1.087µ ±   3%   1.096µ ±   1%       ~ (p=0.058 n=6)
PoolContention/workers-2-4         1.085µ ±   4%   1.097µ ±   4%       ~ (p=0.394 n=6)
PoolContention/workers-4-4         1.084µ ±   1%   1.094µ ±   1%  +0.92% (p=0.011 n=6)
PoolContention/workers-8-4         1.084µ ±   2%   1.097µ ±   6%       ~ (p=0.065 n=6)
PoolContention/workers-16-4        1.089µ ±   1%   1.101µ ±   3%  +1.10% (p=0.011 n=6)
ComponentLifecycle-4               3.604m ±   1%   3.721m ±   1%  +3.24% (p=0.002 n=6)
SourceValidation-4                 2.233µ ±   1%   2.264µ ±   0%  +1.39% (p=0.002 n=6)
RegistryConcurrent-4               766.4n ±   3%   793.2n ±   5%       ~ (p=0.132 n=6)
LoaderLoadFromString-4             3.619m ±   1%   3.691m ±   1%  +1.99% (p=0.002 n=6)
geomean                            17.32µ          17.64µ         +1.87%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.519 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=0.162 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.383 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.861 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.3n ± 0%       ~ (p=0.558 n=6)
CircuitBreakerExecution_Success-4          22.50n ± 0%   22.53n ± 1%       ~ (p=0.502 n=6)
CircuitBreakerExecution_Failure-4          64.27n ± 0%   64.42n ± 0%       ~ (p=0.165 n=6)
geomean                                    74.51n        74.71n       +0.27%

                                  │ 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%   860.3n ± 32%       ~ (p=0.589 n=6)
JQTransform_ObjectConstruction-4         1.408µ ±  0%   1.422µ ± 30%  +0.96% (p=0.009 n=6)
JQTransform_ArraySelect-4                3.255µ ±  0%   3.305µ ±  0%  +1.55% (p=0.002 n=6)
JQTransform_Complex-4                    38.33µ ±  1%   38.27µ ±  0%       ~ (p=0.669 n=6)
JQTransform_Throughput-4                 1.733µ ±  0%   1.763µ ±  2%  +1.76% (p=0.002 n=6)
SSEPublishDelivery-4                     71.99n ±  0%   71.56n ±  1%       ~ (p=0.054 n=6)
geomean                                  1.629µ         1.641µ        +0.74%

                                 │ 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.090µ ± 5%       ~ (p=1.000 n=6)
SchemaValidation_AllFields-4                1.646µ ±  1%   1.665µ ± 3%       ~ (p=0.418 n=6)
SchemaValidation_FormatValidation-4         1.585µ ±  6%   1.585µ ± 1%       ~ (p=0.777 n=6)
SchemaValidation_ManySchemas-4              1.802µ ±  3%   1.779µ ± 5%       ~ (p=0.667 n=6)
geomean                                     1.502µ         1.504µ       +0.10%

                                    │ 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.187µ ± 17%        ~ (p=0.132 n=6)
EventStoreAppend_SQLite-4                  1.732m ±  4%   1.456m ± 13%  -15.95% (p=0.002 n=6)
GetTimeline_InMemory/events-10-4           13.93µ ±  4%   15.28µ ±  2%   +9.69% (p=0.002 n=6)
GetTimeline_InMemory/events-50-4           74.05µ ± 17%   66.02µ ± 23%        ~ (p=0.818 n=6)
GetTimeline_InMemory/events-100-4          124.5µ ±  1%   131.2µ ±  1%   +5.34% (p=0.002 n=6)
GetTimeline_InMemory/events-500-4          640.5µ ±  1%   674.2µ ±  1%   +5.27% (p=0.002 n=6)
GetTimeline_InMemory/events-1000-4         1.327m ±  2%   1.384m ±  1%   +4.27% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             111.7µ ±  1%   115.2µ ±  1%   +3.13% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             256.5µ ±  1%   272.1µ ±  2%   +6.08% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            432.9µ ±  2%   455.9µ ±  1%   +5.32% (p=0.002 n=6)
GetTimeline_SQLite/events-500-4            1.825m ±  1%   1.938m ±  1%   +6.19% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.538m ±  2%   3.736m ±  2%   +5.62% (p=0.002 n=6)
geomean                                    228.1µ         230.1µ         +0.88%

                                   │ baseline-bench.txt │         benchmark-results.txt         │
                                   │        B/op        │     B/op       vs base                │
EventStoreAppend_InMemory-4                  833.0 ± 8%     762.0 ± 10%  -8.52% (p=0.041 n=6)
EventStoreAppend_SQLite-4                  1.984Ki ± 2%   1.982Ki ±  2%       ~ (p=0.937 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.242 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ±  0%       ~ (p=0.636 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.242 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ±  0%  -0.00% (p=0.041 n=6)
geomean                                    67.64Ki        67.14Ki        -0.75%
¹ 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

Fixed in 8fe8a0b. Added isPlaceholderOutputKey and hasDynamicOutputs helpers that detect placeholder keys like (key), (dynamic), and (nested). When a step's inferred outputs contain any such placeholder, validateStepRef now returns early without checking the field name — avoiding false positives for steps like step.secret_fetch whose field names are only known at runtime. Added a regression test TestValidateStepOutputField_DynamicOutputSkipped covering the step.secret_fetch case.

@intel352 intel352 merged commit e081b71 into main Mar 24, 2026
18 checks passed
@intel352 intel352 deleted the copilot/add-cross-reference-checking-validation 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.

wfctl validate: no cross-reference checking for DB columns, step output fields, or imported configs

3 participants