Skip to content

refactor(core): validate config with json schema#1558

Merged
yottahmd merged 2 commits intomainfrom
exec-config-validation
Jan 8, 2026
Merged

refactor(core): validate config with json schema#1558
yottahmd merged 2 commits intomainfrom
exec-config-validation

Conversation

@yottahmd
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd commented Jan 8, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Executor configurations now include validation to catch misconfigurations early with detailed error messages.
    • Configuration validation is automatically performed during workflow execution for all supported executor types.
  • Schema Updates

    • Archive executor configuration now requires the source property.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 8, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Updated the jsonschema-go dependency and introduced a thread-safe schema registry for executor configurations. Added schema validation functions to the core system, integrated validation into the step build pipeline, and registered JSON schemas for nine executor types (archive, docker, gha, hitl, http, jq, mail, ssh).

Changes

Cohort / File(s) Summary
Dependency Update
go.mod
Updated github.com/google/jsonschema-go from v0.3.0 to v0.4.2
Core Schema Registry
internal/core/executor_schema.go, internal/core/executor_schema_test.go
Introduced thread-safe registry for executor config schemas with RegisterExecutorConfigSchema() and ValidateExecutorConfig() functions; includes lazy schema resolution with atomic.Pointer and comprehensive unit tests covering validation, concurrency safety, and edge cases
Step Validation Integration
internal/core/spec/step.go
Integrated executor config validation into step build pipeline to validate configurations against registered schemas during spec processing
Executor Configuration Schemas
internal/runtime/builtin/archive/config.go, internal/runtime/builtin/docker/config.*, internal/runtime/builtin/gha/config.go, internal/runtime/builtin/hitl/config.go, internal/runtime/builtin/http/config.go, internal/runtime/builtin/jq/config.go, internal/runtime/builtin/mail/config.go, internal/runtime/builtin/ssh/config.go
Defined and registered JSON schemas for nine executor types; docker includes test coverage validating schema constraints
JSON Schema Definitions
schemas/dag.schema.json
Made source property required in archiveExecutorConfig schema definition

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant EP as Executor Package<br/>(init)
    participant Registry as Executor Schema<br/>Registry
    participant StepPipeline as Step Build<br/>Pipeline
    participant Validator as Validator

    EP->>Registry: RegisterExecutorConfigSchema("type", schema)
    Note over Registry: Store schema with lazy<br/>resolution support

    App->>StepPipeline: Build Step
    StepPipeline->>Validator: ValidateExecutorConfig(type, config)
    
    alt Schema Registered
        Validator->>Registry: Get schema for type
        Registry->>Validator: Return schemaEntry
        Validator->>Validator: Lazy resolve schema<br/>(first time only)
        Validator->>Validator: Validate config
        alt Valid
            Validator-->>StepPipeline: nil (success)
        else Invalid
            Validator-->>StepPipeline: error with context
        end
    else No Schema
        Validator-->>StepPipeline: nil (skip validation)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the main change: introducing JSON schema validation for executor configurations throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
internal/runtime/builtin/docker/config_test.go (1)

10-58: Well-structured tests with good coverage.

The table-driven test structure follows Go best practices and covers key validation scenarios. The test cases correctly verify the schema constraints defined in config.go (lines 305-318).

Consider adding test cases for additional edge cases:

  • Invalid property types (e.g., "image": 123)
  • Unknown/additional properties
  • Nested object validation (e.g., invalid exec object structure)
Optional: Additional test cases
+		{
+			name:    "invalid image type",
+			config:  map[string]any{"image": 123},
+			wantErr: true,
+		},
+		{
+			name:    "invalid pull policy",
+			config:  map[string]any{"image": "alpine", "pull": "invalid"},
+			wantErr: true,
+		},
internal/runtime/builtin/mail/config.go (1)

11-12: The to field is missing a Type definition.

Unlike from which has Type: "string", the to field has no type constraint. The description mentions it can be "string or array of strings", but without explicit type validation, any value type will be accepted. Consider using OneOf to properly validate both cases:

♻️ Suggested schema for polymorphic `to` field
-		"to":      {Description: "Recipient email address(es) - string or array of strings"},
+		"to": {
+			Description: "Recipient email address(es) - string or array of strings",
+			OneOf: []*jsonschema.Schema{
+				{Type: "string"},
+				{Type: "array", Items: &jsonschema.Schema{Type: "string"}},
+			},
+		},
internal/core/executor_schema_test.go (1)

1-9: Consider adding a test for schema resolution failure.

The tests cover validation scenarios well, but there's no test for the error path when entry.getResolved() fails (line 47-50 in executor_schema.go). This would ensure the "schema error for %s" message is properly formatted.

♻️ Example test for resolution error
func TestValidateExecutorConfig_SchemaResolutionError(t *testing.T) {
	// Create a schema with invalid default that fails resolution
	schema := &jsonschema.Schema{
		Type: "object",
		Properties: map[string]*jsonschema.Schema{
			"value": {Type: "string", Default: 123}, // Invalid default type
		},
	}
	RegisterExecutorConfigSchema("test_resolve_error", schema)

	err := ValidateExecutorConfig("test_resolve_error", map[string]any{})
	require.Error(t, err)
	require.Contains(t, err.Error(), "schema error for test_resolve_error")
}

Based on learnings, tests should cover failure paths.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62abd17 and bec1d5b.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (14)
  • go.mod
  • internal/core/executor_schema.go
  • internal/core/executor_schema_test.go
  • internal/core/spec/step.go
  • internal/runtime/builtin/archive/config.go
  • internal/runtime/builtin/docker/config.go
  • internal/runtime/builtin/docker/config_test.go
  • internal/runtime/builtin/gha/config.go
  • internal/runtime/builtin/hitl/config.go
  • internal/runtime/builtin/http/config.go
  • internal/runtime/builtin/jq/config.go
  • internal/runtime/builtin/mail/config.go
  • internal/runtime/builtin/ssh/config.go
  • schemas/dag.schema.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/runtime/builtin/docker/config_test.go
  • internal/runtime/builtin/archive/config.go
  • internal/runtime/builtin/mail/config.go
  • internal/runtime/builtin/ssh/config.go
  • internal/core/spec/step.go
  • internal/core/executor_schema.go
  • internal/runtime/builtin/hitl/config.go
  • internal/runtime/builtin/gha/config.go
  • internal/runtime/builtin/jq/config.go
  • internal/core/executor_schema_test.go
  • internal/runtime/builtin/docker/config.go
  • internal/runtime/builtin/http/config.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/runtime/builtin/docker/config_test.go
  • internal/core/executor_schema_test.go
🧠 Learnings (4)
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: Applies to **/*_test.go : Co-locate Go tests as `*_test.go`; favour table-driven cases and cover failure paths

Applied to files:

  • internal/runtime/builtin/docker/config_test.go
  • internal/core/executor_schema_test.go
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: Applies to **/*.go : Repository linting relies on `golangci-lint`; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in `internal/common`

Applied to files:

  • go.mod
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: `make test` (or `make test-coverage`) executes the Go suite via `gotestsum`; append `TEST_TARGET=./internal/...` to focus packages

Applied to files:

  • internal/core/executor_schema_test.go
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: Applies to **/*_test.go : Use `stretchr/testify/require` and shared fixtures from `internal/test` instead of duplicating mocks

Applied to files:

  • internal/core/executor_schema_test.go
🧬 Code graph analysis (10)
internal/runtime/builtin/docker/config_test.go (2)
internal/runtime/subcmd.go (1)
  • Run (258-280)
internal/core/executor_schema.go (1)
  • ValidateExecutorConfig (38-57)
internal/runtime/builtin/archive/config.go (1)
internal/core/executor_schema.go (1)
  • RegisterExecutorConfigSchema (30-34)
internal/runtime/builtin/mail/config.go (1)
internal/core/executor_schema.go (1)
  • RegisterExecutorConfigSchema (30-34)
internal/runtime/builtin/ssh/config.go (1)
internal/core/executor_schema.go (1)
  • RegisterExecutorConfigSchema (30-34)
internal/core/spec/step.go (2)
internal/core/step.go (1)
  • ExecutorConfig (195-203)
internal/core/executor_schema.go (1)
  • ValidateExecutorConfig (38-57)
internal/runtime/builtin/hitl/config.go (1)
internal/core/executor_schema.go (1)
  • RegisterExecutorConfigSchema (30-34)
internal/runtime/builtin/gha/config.go (2)
internal/common/logger/tag/tag.go (1)
  • Type (266-268)
internal/core/executor_schema.go (1)
  • RegisterExecutorConfigSchema (30-34)
internal/core/executor_schema_test.go (1)
internal/core/executor_schema.go (2)
  • RegisterExecutorConfigSchema (30-34)
  • ValidateExecutorConfig (38-57)
internal/runtime/builtin/docker/config.go (2)
internal/core/executor_schema.go (1)
  • RegisterExecutorConfigSchema (30-34)
internal/common/logger/tag/tag.go (1)
  • Type (266-268)
internal/runtime/builtin/http/config.go (2)
internal/common/logger/tag/tag.go (1)
  • Type (266-268)
internal/core/executor_schema.go (1)
  • RegisterExecutorConfigSchema (30-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Go Linter
  • GitHub Check: Test on ubuntu-latest
🔇 Additional comments (26)
internal/runtime/builtin/hitl/config.go (1)

28-39: LGTM! Clean schema registration following the established pattern.

The HITL executor schema is correctly defined and registered. The schema fields align with the Config struct and follow the same pattern used by other executors in this PR.

As per coding guidelines, this follows idiomatic Go patterns with proper initialization and integration with the centralized schema registry.

internal/runtime/builtin/jq/config.go (1)

1-17: LGTM! Minimal and clean jq executor schema.

The schema is well-defined with clear description for the raw field and follows the established registration pattern.

As per coding guidelines, this uses idiomatic Go with proper package initialization.

internal/runtime/builtin/http/config.go (1)

8-32: LGTM! Comprehensive HTTP executor schema with appropriate field types.

The schema correctly defines all HTTP executor configuration options with:

  • Proper type definitions (integer, boolean, string, object)
  • Clear descriptions for each field
  • Correct use of AdditionalProperties for headers and query objects to allow arbitrary string key-value pairs

As per coding guidelines, this follows idiomatic Go patterns and integrates properly with the centralized schema registry.

schemas/dag.schema.json (1)

1742-1742: No existing archive executor usage found in the codebase to verify breaking changes against.

No YAML configurations using the archive executor were found in the repository despite searching example files, test configurations, and all documentation. While making source required is technically a breaking change, there are no in-repo configurations that would be affected. If external DAG configurations using the archive executor without source exist, consider documenting this as a breaking change in release notes or migration guides.

go.mod (1)

26-26: Dependency version is valid and secure.

The jsonschema-go v0.4.2 release exists (published Dec 19, 2025) and has no known security advisories.

internal/core/spec/step.go (1)

327-333: LGTM! Well-integrated schema validation.

The executor config validation is correctly placed after all transformations complete and the executor type is determined. The length check prevents unnecessary validation calls for empty configs (initialized on line 259), and error handling is consistent with other validators in the pipeline.

internal/runtime/builtin/docker/config.go (2)

282-285: Good backward compatibility support.

Registering both "docker" and "container" executor types to the same schema ensures backward compatibility while maintaining consistent validation.


287-319: Schema validation complements existing validation logic.

The JSON schema provides declarative validation for structure and basic constraints. The existing validation in LoadConfigFromMap (lines 97-126) handles more complex runtime logic. This is a reasonable separation of concerns.

Note: The schema validates structure (required fields, types, basic constraints) while LoadConfigFromMap handles mode-specific logic (e.g., exec-only-with-containerName on line 124-126). This layered approach is appropriate.

internal/runtime/builtin/ssh/config.go (1)

98-116: LGTM! Schema aligns with Config struct.

The schema definition correctly mirrors the SSH Config struct properties (lines 15-26) with appropriate types and helpful descriptions. The init() function properly registers the schema with the executor registry.

internal/runtime/builtin/archive/config.go (2)

89-109: Schema provides appropriate structural validation.

The schema validates basic structure and constraints (required fields, types, minimum values), while validateConfig (lines 51-87) handles operation-specific validation (e.g., destination required for create operations on line 64). This separation is appropriate.


111-115: Clean helper function and registration.

The ptrFloat helper clearly expresses intent for creating schema constraint pointers. The init() function properly registers the schema with the executor registry.

internal/runtime/builtin/mail/config.go (1)

23-25: LGTM!

The schema registration follows the established pattern and correctly integrates with the central registry.

internal/core/executor_schema_test.go (7)

11-22: LGTM!

Clean test for valid configuration validation. Uses unique schema name and properly asserts no error.


24-37: LGTM!

Good test validating type mismatch detection and error message quality.


39-43: LGTM!

Essential backward compatibility test ensuring unregistered executors pass validation gracefully.


45-57: LGTM!

Good coverage for empty config handling with optional fields.


59-77: LGTM!

Solid concurrency test validating thread-safety of the registry. The 100-goroutine stress test is appropriate for detecting race conditions.


79-98: LGTM!

Good coverage of enum validation for both valid and invalid values.


100-117: LGTM!

Comprehensive test for required field enforcement, covering both missing and present cases.

internal/runtime/builtin/gha/config.go (3)

20-27: Verify if port should be an integer type.

The port field is defined as Type: "string", but ports are typically numeric. If the executor expects a number, consider using Type: "integer". If string is intentional (e.g., for port range syntax like "8080-8090"), consider adding a Pattern constraint for validation.


39-43: LGTM!

Registering the schema under multiple aliases (github_action, github-action, gha) is a good pattern for supporting different naming conventions while maintaining a single source of truth.


8-37: LGTM!

Comprehensive schema definition covering all GHA executor configuration options with proper nested object structures for artifacts and capabilities.

internal/core/executor_schema.go (4)

11-26: LGTM!

Well-designed registry with appropriate concurrency primitives. The combination of sync.RWMutex for registry access and atomic.Pointer with sync.Once for lazy schema resolution is idiomatic Go.


28-34: LGTM!

Clean registration function. Since registration happens via init() functions before any validation occurs, the simple replacement semantics are appropriate.


36-57: LGTM!

Correct implementation with proper lock scoping. The read lock is released before calling getResolved(), which has its own synchronization, avoiding potential lock ordering issues.


59-75: LGTM!

The lazy resolution pattern using sync.Once combined with atomic.Pointer is correctly implemented. The sync.Once ensures thread-safe one-time initialization, and subsequent calls correctly return the cached result or error.

@yottahmd yottahmd merged commit 67191a9 into main Jan 8, 2026
4 checks passed
@yottahmd yottahmd deleted the exec-config-validation branch January 8, 2026 16:51
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 8, 2026

Codecov Report

❌ Patch coverage is 82.35294% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.72%. Comparing base (09736db) to head (51e26a9).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
internal/core/executor_schema.go 75.00% 4 Missing and 3 partials ⚠️
internal/core/spec/step.go 33.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1558      +/-   ##
==========================================
+ Coverage   63.70%   63.72%   +0.02%     
==========================================
  Files         226      231       +5     
  Lines       25187    25238      +51     
==========================================
+ Hits        16045    16084      +39     
- Misses       7660     7665       +5     
- Partials     1482     1489       +7     
Files with missing lines Coverage Δ
internal/runtime/builtin/archive/config.go 51.28% <100.00%> (+4.05%) ⬆️
internal/runtime/builtin/docker/config.go 85.61% <100.00%> (+0.30%) ⬆️
internal/runtime/builtin/gha/config.go 100.00% <100.00%> (ø)
internal/runtime/builtin/hitl/config.go 100.00% <100.00%> (ø)
internal/runtime/builtin/http/config.go 100.00% <100.00%> (ø)
internal/runtime/builtin/jq/config.go 100.00% <100.00%> (ø)
internal/runtime/builtin/mail/config.go 100.00% <100.00%> (ø)
internal/runtime/builtin/ssh/config.go 81.13% <100.00%> (+0.73%) ⬆️
internal/core/spec/step.go 85.79% <33.33%> (-0.21%) ⬇️
internal/core/executor_schema.go 75.00% <75.00%> (ø)

... and 3 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 62abd17...51e26a9. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

1 participant