Skip to content

Engine runtime validation: reuse wfctl cross-reference checks at startup#377

Merged
intel352 merged 4 commits intomainfrom
copilot/reuse-wfctl-cross-reference-checks
Mar 25, 2026
Merged

Engine runtime validation: reuse wfctl cross-reference checks at startup#377
intel352 merged 4 commits intomainfrom
copilot/reuse-wfctl-cross-reference-checks

Conversation

Copy link
Contributor

Copilot AI commented Mar 24, 2026

Not every team runs wfctl validate in CI — the engine should protect itself. This extracts the pipeline template cross-reference logic from wfctl into a shared validation package and wires it into BuildFromConfig() with configurable strictness.

Changes

New validation/ package

  • ValidatePipelineTemplateRefs(pipelines map[string]any) *RefValidationResult — shared validation callable by both wfctl and the engine
  • Checks: dangling step refs, forward refs, self-refs, output field mismatches against step schemas, SQL column validation for step.db_query
  • ExtractSQLColumns() moved here from cmd/wfctl/api_extract.go (was duplicated in two places)

config/config.go

New Engine *EngineConfig field on WorkflowConfig:

engine:
  validation:
    templateRefs: warn   # "off" | "warn" | "error" (default: "warn")

engine.go — startup validation in BuildFromConfig()

  • warn (default): logs warnings, engine starts — backwards compatible, no functional change for existing configs
  • error: returns error and refuses to start if validation finds issues
  • off: skips entirely, exact prior behaviour

cmd/wfctl/template_validate.go + api_extract.go

  • Removed ~375 lines of duplicate validation logic; both now delegate to the shared validation package

Example

# engine refuses to start — step "lookup" doesn't exist in this pipeline
engine:
  validation:
    templateRefs: error

pipelines:
  get-user:
    steps:
      - name: respond
        type: step.json_response
        config:
          body: '{{ .steps.lookup.row.id }}'   # "lookup" is not a declared step
Original prompt

This section details on the original issue you should resolve

<issue_title>Engine runtime validation: reuse wfctl cross-reference checks at startup and execution time</issue_title>
<issue_description>## Summary

Follow-up to #368 (wfctl static cross-reference validation) and #374 (output type enforcement).

#368 proposes deep template reference validation in wfctl validate — checking field-level references against step output schemas, SQL alias extraction, and cross-file import validation. That same validation logic should also be available inside the engine itself, so that problems are caught automatically at startup or first execution without requiring a separate wfctl validate CI step.

Motivation

  • Not every team runs wfctl validate in CI — the engine should protect itself
  • Static analysis catches config-time issues, but some references are only resolvable at runtime (dynamic SQL, template-generated step names, per-tenant configs)
  • A config that passes wfctl validate could still fail at runtime due to environment-specific differences (env var expansion, imported file resolution)
  • Developers iterating locally benefit from immediate feedback on startup rather than waiting for the first request to hit a broken pipeline

Proposed behavior

Phase 1: Startup validation (config load time)

When the engine loads config via BuildFromConfig(), run the same checks that wfctl template validate performs:

  1. Step name references — already done in wfctl, reuse in engine
  2. Step output field references — validate that steps.query.row references a field declared in step.db_query's output schema (proposed in wfctl validate: no cross-reference checking for DB columns, step output fields, or imported configs #368)
  3. Cross-file imports — verify all referenced pipelines/steps exist in the merged config
  4. Mode-dependent output validation — e.g., steps.query.rows on a mode: single db_query

This should be warnings by default to maintain backwards compatibility:

engine:
  validation:
    template_refs: warn   # "off" | "warn" | "error" (default: "warn")

With warn (default): log warnings at startup for any suspicious references, but allow the engine to start. With error: refuse to start if validation fails. With off: skip entirely (current behavior).

Phase 2: First-execution validation (runtime)

Some references can only be validated when a pipeline actually runs — for example, when step outputs are known. On first execution of each pipeline, the engine could:

  1. After each step completes, validate that its actual output keys match the declared StepSchema.Outputs
  2. Before resolving a template, check that the referenced step has already produced the expected output keys
  3. Log warnings (or error, based on config) when a template resolves a missing key to zero value

This provides a runtime safety net that catches issues static analysis cannot:

WARN [pipeline=get-form step=respond] template references steps.query.row.slug
     but step "query" output keys are: [row, found] — "slug" is a sub-key of "row"
     which cannot be statically verified (depends on SQL result)

Implementation: share validation logic

The key principle is one validation library, two consumers:

wfctl/template_validate.go  ──┐
                               ├── shared validation package
engine/pipeline_executor.go ──┘

The validation logic proposed in #368 (deep template ref checking, output schema lookup, SQL alias extraction) should live in a shared package (e.g., validation/ or within config/) that both wfctl and the engine import. This avoids duplicating logic and ensures consistency.

// validation/template_refs.go (shared)
type RefValidationResult struct {
    Warnings []string
    Errors   []string
}

func ValidateTemplateRefs(pipelines map[string]PipelineConfig, schemas map[string]StepSchema) *RefValidationResult

// Used by wfctl:
//   result := validation.ValidateTemplateRefs(cfg.Pipelines, registry.Schemas())
//   printWarnings(result)

// Used by engine at startup:
//   result := validation.ValidateTemplateRefs(cfg.Pipelines, registry.Schemas())
//   if engineConfig.Validation.TemplateRefs == "error" && len(result.Errors) > 0 {
//       return fmt.Errorf("template validation failed: %v", result.Errors)
//   }
//   for _, w := range result.Warnings { logger.Warn(w) }

Backwards compatibility

  • Default behavior (warn) only adds log output — no functional change
  • off preserves exact current behavior
  • error is opt-in for teams that want strict validation
  • No existing configs break

References


💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

Copilot AI changed the title [WIP] Implement engine runtime validation for wfctl cross-reference checks Engine runtime validation: reuse wfctl cross-reference checks at startup Mar 24, 2026
Copilot AI requested a review from intel352 March 24, 2026 23:22
@intel352 intel352 marked this pull request as ready for review March 25, 2026 00:47
Copilot AI review requested due to automatic review settings March 25, 2026 00:47
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 factors pipeline template cross-reference validation out of wfctl into a shared validation package, and runs that same validation during engine startup (BuildFromConfig) with configurable strictness via engine.validation.templateRefs.

Changes:

  • Added validation/ package for pipeline template reference checks and SQL column extraction.
  • Added engine.validation.templateRefs config and wired startup validation into StdEngine.BuildFromConfig().
  • Updated wfctl to delegate to the shared validation package; added/updated tests and documentation.

Reviewed changes

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

Show a summary per file
File Description
validation/pipeline_refs.go New shared implementation of template cross-reference validation + SQL column extraction.
validation/pipeline_refs_test.go Unit tests covering reference checks and SQL column extraction.
engine.go Runs template ref validation at startup with off/warn/error modes.
config/config.go Adds EngineConfig / EngineValidationConfig to WorkflowConfig.
engine_validation_test.go Tests engine startup behavior across validation modes.
cmd/wfctl/template_validate.go Replaces in-file validation logic with calls into validation package.
cmd/wfctl/template_validate_test.go Updates tests to validate via the shared validation package.
cmd/wfctl/api_extract.go Uses validation.ExtractSQLColumns instead of a local duplicate.
DOCUMENTATION.md Documents the new engine.validation.templateRefs setting.

mode := "warn" // default
if cfg.Engine != nil && cfg.Engine.Validation != nil && cfg.Engine.Validation.TemplateRefs != "" {
mode = cfg.Engine.Validation.TemplateRefs
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

engine.validation.templateRefs accepts only "off" | "warn" | "error", but BuildFromConfig treats any other value as "warn" (since only "off" and "error" are handled specially). This makes typos silently change behavior. Consider validating the value explicitly and either returning a config error (preferred) or logging a warning and falling back to the default.

Suggested change
}
}
switch mode {
case "off", "warn", "error":
// valid
default:
return fmt.Errorf("invalid engine.validation.templateRefs value %q, must be one of: off, warn, error", mode)
}

Copilot uses AI. Check for mistakes.
Comment on lines +109 to +113
stepInfos := make(map[string]stepBuildInfo) // step name -> type and config (used by validateStepRef)

reg := schema.NewStepSchemaRegistry()

for i, stepRaw := range stepsRaw {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

validatePipelineTemplateRefs creates a fresh schema.NewStepSchemaRegistry() for every pipeline. This both rebuilds built-in schemas repeatedly and (more importantly) ignores any step schemas registered by loaded plugins (the engine’s PluginLoader().StepSchemaRegistry()), so output-field validation won’t run for plugin step types even when schemas are available. Consider accepting a *schema.StepSchemaRegistry as an input (or otherwise wiring the engine/plugin loader registry through) and reusing a single instance across all pipelines.

Copilot uses AI. Check for mistakes.
Comment on lines +163 to +172
// Check for step name references via dot-access (captures optional field path)
dotMatches := stepRefDotRe.FindAllStringSubmatch(actionContent, -1)
for _, m := range dotMatches {
refName := m[1]
fieldPath := ""
if len(m) > 2 {
fieldPath = m[2]
}
validateStepRef(pipelineName, stepName, refName, fieldPath, i, stepNames, stepInfos, reg, result)
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

When actionContent contains a hyphenated dot-access segment (matched by hyphenDotRe), stepRefDotRe can still partially match and pass a truncated fieldPath into validateStepRef, which can produce misleading extra warnings (e.g. spurious unknown output / SQL-column warnings) in addition to the intended “hyphenated dot-access” warning. Consider skipping field-path validation for dot-matches when hyphenDotRe matches the action (still validating that the referenced step exists/ordering), so the linter emits only the relevant hyphen guidance.

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

github-actions bot commented Mar 25, 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:312720: parsing iteration count: invalid syntax
baseline-bench.txt:630001: parsing iteration count: invalid syntax
baseline-bench.txt:935006: parsing iteration count: invalid syntax
baseline-bench.txt:1239261: parsing iteration count: invalid syntax
baseline-bench.txt:1572928: parsing iteration count: invalid syntax
benchmark-results.txt:245: parsing iteration count: invalid syntax
benchmark-results.txt:308370: parsing iteration count: invalid syntax
benchmark-results.txt:624292: parsing iteration count: invalid syntax
benchmark-results.txt:923687: parsing iteration count: invalid syntax
benchmark-results.txt:1235484: parsing iteration count: invalid syntax
benchmark-results.txt:1549696: 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.235m ± 203%   3.102m ± 216%       ~ (p=0.589 n=6)
ComponentLoad-4                    3.501m ±   1%   3.626m ±   6%  +3.57% (p=0.002 n=6)
ComponentExecute-4                 1.890µ ±   1%   1.962µ ±   1%  +3.84% (p=0.002 n=6)
PoolContention/workers-1-4         1.061µ ±   2%   1.086µ ±   3%  +2.36% (p=0.002 n=6)
PoolContention/workers-2-4         1.065µ ±   1%   1.094µ ±   2%  +2.68% (p=0.002 n=6)
PoolContention/workers-4-4         1.054µ ±   3%   1.103µ ±   1%  +4.55% (p=0.002 n=6)
PoolContention/workers-8-4         1.063µ ±   1%   1.101µ ±   1%  +3.53% (p=0.002 n=6)
PoolContention/workers-16-4        1.064µ ±   3%   1.093µ ±   1%  +2.77% (p=0.006 n=6)
ComponentLifecycle-4               3.472m ±   1%   3.636m ±   2%  +4.73% (p=0.002 n=6)
SourceValidation-4                 2.185µ ±   1%   2.236µ ±   1%  +2.33% (p=0.002 n=6)
RegistryConcurrent-4               764.8n ±   6%   789.3n ±   4%       ~ (p=0.589 n=6)
LoaderLoadFromString-4             3.524m ±   2%   3.669m ±   2%  +4.10% (p=0.002 n=6)
geomean                            17.02µ          17.49µ         +2.77%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.554 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=0.669 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.853 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.084 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                 279.9n ± 19%   286.0n ± 4%       ~ (p=0.058 n=6)
CircuitBreakerExecution_Success-4         21.84n ±  1%   22.49n ± 0%  +2.98% (p=0.002 n=6)
CircuitBreakerExecution_Failure-4         63.12n ±  1%   64.30n ± 0%  +1.86% (p=0.002 n=6)
geomean                                   72.81n         74.51n       +2.33%

                                  │ 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                     830.5n ± 26%   850.9n ± 29%       ~ (p=0.132 n=6)
JQTransform_ObjectConstruction-4         1.349µ ±  2%   1.397µ ±  0%  +3.60% (p=0.002 n=6)
JQTransform_ArraySelect-4                3.147µ ±  2%   3.264µ ±  0%  +3.72% (p=0.002 n=6)
JQTransform_Complex-4                    36.33µ ±  2%   38.16µ ±  3%  +5.04% (p=0.002 n=6)
JQTransform_Throughput-4                 1.673µ ±  1%   1.816µ ±  1%  +8.52% (p=0.002 n=6)
SSEPublishDelivery-4                     68.25n ±  2%   69.85n ±  0%  +2.36% (p=0.002 n=6)
geomean                                  1.564µ         1.630µ        +4.26%

                                 │ 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.089µ ± 3%   1.105µ ± 9%       ~ (p=0.310 n=6)
SchemaValidation_AllFields-4                 1.651µ ± 7%   1.678µ ± 3%       ~ (p=0.485 n=6)
SchemaValidation_FormatValidation-4          1.571µ ± 2%   1.575µ ± 1%       ~ (p=0.723 n=6)
SchemaValidation_ManySchemas-4               1.789µ ± 3%   1.787µ ± 2%       ~ (p=0.851 n=6)
geomean                                      1.499µ        1.511µ       +0.79%

                                    │ 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.215µ ± 21%   1.190µ ± 28%       ~ (p=0.699 n=6)
EventStoreAppend_SQLite-4                  1.267m ±  6%   1.370m ±  3%  +8.19% (p=0.004 n=6)
GetTimeline_InMemory/events-10-4           14.34µ ±  3%   14.05µ ±  2%       ~ (p=0.132 n=6)
GetTimeline_InMemory/events-50-4           78.55µ ±  3%   76.58µ ±  5%       ~ (p=0.240 n=6)
GetTimeline_InMemory/events-100-4          125.5µ ± 21%   135.4µ ± 14%       ~ (p=0.937 n=6)
GetTimeline_InMemory/events-500-4          643.8µ ±  1%   629.5µ ±  0%  -2.23% (p=0.002 n=6)
GetTimeline_InMemory/events-1000-4         1.328m ±  2%   1.282m ±  1%  -3.48% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             108.2µ ±  1%   108.7µ ±  0%       ~ (p=0.180 n=6)
GetTimeline_SQLite/events-50-4             248.9µ ±  2%   248.4µ ±  2%       ~ (p=1.000 n=6)
GetTimeline_SQLite/events-100-4            420.0µ ±  1%   425.6µ ±  2%  +1.34% (p=0.015 n=6)
GetTimeline_SQLite/events-500-4            1.783m ±  2%   1.794m ±  2%       ~ (p=0.180 n=6)
GetTimeline_SQLite/events-1000-4           3.482m ±  1%   3.477m ±  1%       ~ (p=1.000 n=6)
geomean                                    220.3µ         221.2µ        +0.43%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  817.5 ± 9%     824.0 ± 7%       ~ (p=0.589 n=6)
EventStoreAppend_SQLite-4                  1.989Ki ± 1%   1.984Ki ± 2%       ~ (p=0.468 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.080 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ± 0%  +0.00% (p=0.032 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.145 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%       ~ (p=0.076 n=6)
geomean                                    67.55Ki        67.58Ki       +0.04%
¹ 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

…cture, accept registry param, fix hyphen spurious warnings

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoCodeAlone/workflow/sessions/b13a8553-fb55-4892-a6e4-21e323b5cb20
Copy link
Contributor Author

Copilot AI commented Mar 25, 2026

@copilot apply changes based on the comments in this thread

Applied all five review comments in commit 379bf48:

  1. engine.go — Added explicit switch validation for templateRefs; returns fmt.Errorf("invalid engine.validation.templateRefs value %q, must be one of: off, warn, error", mode) for any unrecognised value. Added TestEngine_BuildFromConfig_InvalidTemplateRefsMode to cover this.

  2. config/config.go — Updated the TemplateRefs comment to say "fail on any validation issues" (since the validator only populates Warnings today, error mode treats all warnings as fatal).

  3. DOCUMENTATION.md — Restored the ## Visual Workflow Builder (UI) section header and promoted the Engine Validation Config section to a peer ## heading.

  4. validation/pipeline_refs.goValidatePipelineTemplateRefs now accepts an optional *schema.StepSchemaRegistry variadic parameter. When provided it's used directly; otherwise a single default registry is created once and reused across all pipelines (no per-pipeline rebuild). The engine now passes e.PluginLoader().StepSchemaRegistry() so plugin-registered step schemas are included.

  5. validation/pipeline_refs.go — The dot-match loop now computes hasHyphen := hyphenDotRe.MatchString(actionContent) once and skips fieldPath when it's true, preventing spurious output-field/SQL-column warnings alongside the intentional hyphen warning. TestValidatePipelineTemplateRefs_HyphenatedStep extended to assert no such spurious warnings appear.

@intel352 intel352 merged commit 83cbc22 into main Mar 25, 2026
18 checks passed
@intel352 intel352 deleted the copilot/reuse-wfctl-cross-reference-checks branch March 25, 2026 01:37
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.

Engine runtime validation: reuse wfctl cross-reference checks at startup and execution time

3 participants