Skip to content

feat: add action schema v2#2122

Open
yottahmd wants to merge 21 commits into
mainfrom
codex/action-schema-v2
Open

feat: add action schema v2#2122
yottahmd wants to merge 21 commits into
mainfrom
codex/action-schema-v2

Conversation

@yottahmd
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd commented May 7, 2026

Summary

  • add canonical action/with step schema normalization with legacy compatibility
  • add validate-only deprecation warnings for legacy step execution fields
  • update JSON schema, parser validation, executor mapping tests, and README examples
  • migrate existing test fixtures, integration DAGs, e2e fixtures, and embedded examples to the new run / action syntax while keeping legacy syntax only in compatibility/deprecation tests

Testing

  • go test ./internal/cmd ./internal/core/spec ./internal/cmn/schema ./internal/service/frontend/api/v1 ./internal/runtime/executor ./internal/persis/filedag ./internal/agent ./internal/intg/embed -count=1
  • go test ./internal/intg/distr -run 'Test(CustomStepTypes_|.*SubDAG|Parallel|Lifecycle|Params|BaseConfig)' -count=1
  • go test ./internal/intg -run 'TestRetryRestoresHarnessConfigFromBaseConfig|TestSSHExecutorIntegration/(ErrorHandling_InvalidWorkingDirectory|StepLevelSSHConfig)|TestTemplateExecutor/(SlimSprigOverlapBehavior|SlimSprigMissingKeyBoundary|DataFromPriorStep|RelativeOutputPath|ArtifactOutputAutoEnablesArtifacts)|TestCommandExecution_DollarEscape/ScriptWithoutShell_Direct|TestDAGExecution/(PerlScript|SpecialVars|CommandErrorIncludesLastStderrLine)' -count=1 -v
  • go test ./internal/intg -count=1 -json
  • go test ./internal/... -count=1
  • pnpm test -- src/features/dags/components/dag-editor/__tests__/customStepSchema.test.ts src/features/dags/components/dag-details/__tests__/DAGStepTableRow.test.tsx src/features/dags/components/dag-details/__tests__/HarnessStepSummary.test.tsx src/features/dags/components/visualization/__tests__/TimelineChart.test.tsx src/features/dags/components/visualization/__tests__/timelineItems.test.ts

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced v2 step execution syntax using run and action fields with full backward compatibility.
    • Added custom actions support for reusable action definitions.
    • Validation now emits deprecation warnings for legacy step syntax while continuing to validate successfully.
  • Documentation

    • Updated examples to reflect v2 step syntax patterns and available action types.
  • Bug Fixes

    • HTTP requests now properly fall back to configuration values when command-derived fields are empty.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 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.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 492b2a68-a7d4-4bdc-b97d-dbac04e5e4d6

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a canonical v2 step execution model using run (local shell/script) and action (named executors) fields. The schema adds an actions section for reusable custom action definitions, marks legacy execution fields as deprecated, and enforces mutual exclusivity. A new deprecation detection system emits warnings for legacy syntax. Core normalization logic converts v2 syntax to internal executor representation and supports 15+ built-in actions. Custom step type and action registry now distinguishes between the two kinds. Builder and loader integrate normalization early. CLI validation emits deprecation warnings. Comprehensive tests validate schema and parsing across all action types. README examples demonstrate the v2 migration.

Changes

V2 Execution Model Implementation

Layer / File(s) Summary
Schema & Data Structures
internal/cmn/schema/dag.schema.json, internal/core/spec/dag.go, internal/core/spec/step.go, internal/runtime/builtin/http/config.go
DAG schema adds run and action as canonical v2 fields, deprecates legacy command/exec/shell/call/params/type, introduces actions top-level block for custom definitions, and HTTP config gains method/url properties; step struct adds Run field, dag struct adds Actions field.
Deprecation Warning Detection
internal/core/spec/deprecation.go, internal/core/spec/step_v2_cleanup_test.go
New module parses YAML streams document-by-document, scans steps and handlers for deprecated step_types, legacy execution fields, and misconfigurations, emits deterministically ordered warnings; tests verify warning determinism and schema-code consistency.
V2 Step Normalization
internal/core/spec/step_v2.go
Introduces normalizeStepExecutionRaw entry point and per-action normalizers for 15+ built-in actions (http.request, dag.run, docker.run, jq.filter, postgres.import, harness.run, redis operations, etc.) and custom actions; validates run/action mutual exclusivity with legacy fields; normalizes with parameters per action type.
Custom Actions & Step Types Registry
internal/core/spec/step_types.go
Refactors registry to distinguish step_type (kind=step_type) from action (kind=action); validates actions require input_schema and template with exactly one run or action field; introduces dot-separated action naming patterns; builds unified registry for both kinds.
Builder & Loader Integration
internal/core/spec/builder.go, internal/core/spec/loader.go
builder.go applies normalizeStepExecutionRaw before decoding raw specs; loader.go uses buildCustomStepActionRegistry to construct unified registry from step types and actions via new actionsOf helper.
Validation & Error Messages
internal/core/validator.go, internal/core/parallel_test.go, internal/runtime/builtin/http/http.go
Updates parallel validation to reference action: dag.run syntax; HTTP executor implements fallback to config values for method/url when command-derived fields are empty; test assertions updated.
CLI Validation Command
internal/cmd/validate.go, internal/cmd/validate_test.go
Collects and logs deprecation warnings via collectDeprecatedSyntaxWarnings after DAG load from main YAML and local DAGs with deterministic ordering; tests cover legacy syntax (warns but succeeds), v2 syntax (silent success), and mixed syntax (fails).
Comprehensive Testing
internal/cmn/schema/dag_schema_test.go, internal/core/spec/step_v2_test.go, internal/core/validator_test.go
DAG schema test validates V2 variants; 13 step V2 tests cover run parsing, action: dag.run parallel, http.request/docker.run/jq.filter/postgres.import/harness.run executors, custom actions, and handlers; validates both acceptance and rejection of incompatible field combinations.
Documentation Migration
README.md
Updates 12+ workflow examples to v2 syntax: sequential/parallel/docker/SSH/sub-DAG/retry/scheduling/approval examples migrate from legacy command/type to run/action/with; custom action examples demonstrate templating; index renames "Step Types" to "Actions".

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dagucloud/dagu#2021: Both PRs normalize step-level executor configuration by introducing canonical with field and deprecating legacy config alias, so they coordinate on schema and step parsing.
  • dagucloud/dagu#1978: Both PRs modify harness executor surface including providers, schemas, step normalization, and provider registry, so they touch the same feature at code level.
  • dagucloud/dagu#2098: Both PRs update step validation and error messaging in internal/core/validator.go and related validation tests.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR description covers summary, testing approach, and technical scope but lacks formal structured sections matching the template. Reorganize description to explicitly include 'Summary', 'Changes', 'Related Issues', and 'Checklist' sections as specified in the template for better consistency and completeness.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add action schema v2' accurately describes the main change: introducing a v2 action/with step schema normalization system with comprehensive supporting updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/action-schema-v2

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.

@yottahmd yottahmd added this to the v2.7.0 milestone May 7, 2026
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: 7

🧹 Nitpick comments (1)
internal/core/spec/builder.go (1)

356-371: ⚡ Quick win

normalizeStepExecutionRaw is always called even when the input is already normalised.

When buildStepFromSpec is invoked from buildStepFromRaw, the raw argument is already the output of normalizeStepExecutionRaw. The function is called a second time here unconditionally, but its result is only consumed when hasRun || hasAction is true. If normalisation removes run/action keys (the expected behaviour), those keys will be absent, the inner block never executes, and the second call's result is silently discarded.

Consider moving the call inside the guard to make the intent explicit and avoid redundant work on the hot step-building path:

♻️ Suggested restructure
-	if raw != nil {
-		_, hasRun := raw["run"]
-		_, hasAction := raw["action"]
-		normalizedRaw, err := normalizeStepExecutionRaw(raw, ctx.customStepTypes)
-		if err != nil {
-			return nil, err
-		}
-		if hasRun || hasAction {
-			normalizedSpec, err := decodeStep(normalizedRaw)
-			if err != nil {
-				return nil, err
-			}
-			st = normalizedSpec
-			raw = normalizedRaw
-		}
-	}
+	if raw != nil {
+		_, hasRun := raw["run"]
+		_, hasAction := raw["action"]
+		if hasRun || hasAction {
+			normalizedRaw, err := normalizeStepExecutionRaw(raw, ctx.customStepTypes)
+			if err != nil {
+				return nil, err
+			}
+			normalizedSpec, err := decodeStep(normalizedRaw)
+			if err != nil {
+				return nil, err
+			}
+			st = normalizedSpec
+			raw = normalizedRaw
+		}
+	}

Note: if normalizeStepExecutionRaw validates legacy fields even in the absence of run/action and you rely on those errors being surfaced here, the unconditional call is intentional and the above refactor would suppress them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/core/spec/builder.go` around lines 356 - 371, The code
unconditionally calls normalizeStepExecutionRaw inside buildStepFromSpec even
though buildStepFromRaw already provided normalized input, causing wasted work
and discarded results when run/action are absent; move the call to
normalizeStepExecutionRaw so it only runs inside the hasRun || hasAction guard
(i.e., call normalizeStepExecutionRaw just before decodeStep and assign
normalizedRaw to raw only when hasRun || hasAction), and preserve existing error
propagation from normalizeStepExecutionRaw; if you intentionally rely on its
validation even when run/action are missing, add a comment explaining that and
keep the unconditional call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cmn/schema/dag.schema.json`:
- Around line 1919-1968: The schema for steps is too permissive compared to the
loader's normalizeStepExecutionRaw logic: update the allOf/if-then blocks so the
schema rejects the same invalid combinations and enforces the same
action-specific "with" shapes; specifically, mirror normalizeStepExecutionRaw by
adding constraints that disallow "exec" and "shell_args" when "run" is present
(use a "not": {"required": ["run","exec"]} / similar for "shell_args"), disallow
"command"/"call"/"script" when "action" is present, and add additional
action-specific if-then clauses (e.g., for "validate" and any other action types
handled in code) that require "with" and point to the correct action config defs
(in addition to the existing dagRunActionConfig and httpRequestActionConfig),
and ensure the "run" branch continues to reference runWithConfig; align property
names and required sets to match normalizeStepExecutionRaw exactly.
- Around line 5479-5499: The JSON schema for customActionDefinition does not
include output_schema even though validateCustomActionSpec and
buildCustomStepFromSpec accept and apply actions.*.output_schema; add an
"output_schema" property to the customActionDefinition properties (mirroring
input_schema) with type "object", additionalProperties: true and a descriptive
"description" so the schema permits actions.*.output_schema without breaking
validation; update the customActionDefinition properties block to include this
new symbol.

In `@internal/core/spec/deprecation.go`:
- Around line 47-64: The loop in DeprecatedSyntaxWarnings currently returns nil
on a non-EOF decode error, discarding warnings already collected; change the
error branch so that instead of returning nil you return the accumulated
warnings slice (i.e., return warnings) so partial warnings from earlier decoded
documents are preserved. Locate the decoder.Decode(&doc) error handling in the
DeprecatedSyntaxWarnings function and replace the "return nil" with "return
warnings" while leaving the EOF break behavior unchanged.

In `@internal/core/spec/step_types.go`:
- Around line 368-383: The validation currently only ensures exactly one of
spec.Template["run"] or spec.Template["action"] is present; extend the check in
the same block (after the run/action exclusivity check) to scan spec.Template
for legacy execution keys (e.g., "type", "command", "shell_args" and any other
deprecated execution keys) and if any are present return
core.NewValidationError(fmt.Sprintf("actions.%s.template", name), spec.Template,
fmt.Errorf("template contains deprecated execution keys: %v", invalidKeys)) so
the error surface lists the offending keys; keep the existing NewValidationError
usage and message style and ensure the scan references spec.Template and the
same name variable.
- Around line 925-928: The normalization call uses
normalizeStepExecutionRaw(mergedRaw, nil) which passes a nil custom-action
registry and causes rendered action references like customActionTemplate.action
-> "action: my.custom.action" to resolve as unknown; update the call to pass the
active custom-action registry (the same registry used to validate/build custom
types) instead of nil so normalizeStepExecutionRaw can resolve referenced custom
actions during decode/build (preserve existing error wrapping that uses
customType.Name).

In `@internal/runtime/builtin/http/http.go`:
- Around line 90-95: newHTTP currently falls back to reqCfg.Method and
reqCfg.URL but doesn't validate that method and url are non-empty; this can lead
to obscure runtime errors ("parse empty URL"/"invalid method"). After the
existing fallback (the block that sets method = reqCfg.Method and url =
reqCfg.URL), add a guard in newHTTP that checks if method == "" or url == "" and
return an explicit error (including which field is missing) (use the same
error-returning convention as newHTTP) so callers get a clear diagnostic instead
of a low-level resty/http error.

In `@README.md`:
- Around line 497-499: The table row is ambiguous because it lists "run" as if
it were an action name; update the table so it clearly distinguishes the YAML
field `run:` from action names like `docker.run` and `ssh.run` — for example,
change the first column text to "`run:` field" or "Field / Keyword" (instead of
"Action") and/or add a short parenthetical note in that row like "(YAML `run:`
field for shell steps)" so readers won't try `action: run`; ensure the change
updates the row containing `run` and keeps other rows referencing `docker.run`
and `ssh.run` unchanged.

---

Nitpick comments:
In `@internal/core/spec/builder.go`:
- Around line 356-371: The code unconditionally calls normalizeStepExecutionRaw
inside buildStepFromSpec even though buildStepFromRaw already provided
normalized input, causing wasted work and discarded results when run/action are
absent; move the call to normalizeStepExecutionRaw so it only runs inside the
hasRun || hasAction guard (i.e., call normalizeStepExecutionRaw just before
decodeStep and assign normalizedRaw to raw only when hasRun || hasAction), and
preserve existing error propagation from normalizeStepExecutionRaw; if you
intentionally rely on its validation even when run/action are missing, add a
comment explaining that and keep the unconditional call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b679a32-c8cc-4814-a3aa-a648b00f7fdb

📥 Commits

Reviewing files that changed from the base of the PR and between dbece5a and 78d1730.

📒 Files selected for processing (19)
  • README.md
  • internal/cmd/validate.go
  • internal/cmd/validate_test.go
  • internal/cmn/schema/dag.schema.json
  • internal/cmn/schema/dag_schema_test.go
  • internal/core/parallel_test.go
  • internal/core/spec/builder.go
  • internal/core/spec/dag.go
  • internal/core/spec/deprecation.go
  • internal/core/spec/loader.go
  • internal/core/spec/step.go
  • internal/core/spec/step_types.go
  • internal/core/spec/step_v2.go
  • internal/core/spec/step_v2_cleanup_test.go
  • internal/core/spec/step_v2_test.go
  • internal/core/validator.go
  • internal/core/validator_test.go
  • internal/runtime/builtin/http/config.go
  • internal/runtime/builtin/http/http.go

Comment thread internal/cmn/schema/dag.schema.json
Comment thread internal/cmn/schema/dag.schema.json
Comment thread internal/core/spec/deprecation.go
Comment thread internal/core/spec/step_types.go
Comment thread internal/core/spec/step_types.go Outdated
Comment thread internal/runtime/builtin/http/http.go
Comment thread README.md Outdated
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