Skip to content

fix(cloud): reject malformed executionOrder instead of silently runni… - #117

Merged
finalerock44 merged 1 commit into
devfrom
fix/110-workspace-config-validation
Aug 13, 2026
Merged

fix(cloud): reject malformed executionOrder instead of silently runni…#117
finalerock44 merged 1 commit into
devfrom
fix/110-workspace-config-validation

Conversation

@finalerock44

@finalerock44 finalerock44 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

…ng in parallel

A workspace config.yaml was yaml.load'ed and straight-cast to IWorkspaceConfig, so a wrong-shaped executionOrder was never checked. The intuitive bare-list form made executionOrder an Array, .flowsOrder came back undefined, resolveSequentialFlows returned [], and every flow ran in parallel - same cost, wrong semantics, green run. The only symptom was depends_on being null on every result row.

Add a zod schema as the single source of truth for the config shape (src/services/workspace-config.schema.ts) and route all three former cast sites through one validated loader, loadWorkspaceConfig:

  • A malformed executionOrder is now fatal (exit 1), with a message showing what was found next to the expected shape. A bare list is not valid Maestro either, so there is nothing to accept - and a warning in CI logs is exactly what got missed.
  • Unrecognised top-level keys warn (and are preserved, since the config is forwarded to the API as fields.workspaceConfig), catching flowOrder, a top-level continueOnFailure, tags in place of includeTags, and flowTimeout.
  • executionOrder on a single-file input warns instead of being dropped: planSingleFile never sequences, so it was silently ignored even when well-formed.
  • continueOnFailure's real default (true) now lives in the schema instead of being re-specified at three read sites.
  • WORKSPACE_CONFIG_KEYS is derived from the schema so isWorkspaceConfigFile's detection set can no longer drift from it.
  • includeTags/excludeTags scalar coercion moves from readYamlFileAsJson into the schema, so the loader is a plain YAML read and the validator is pure.

Warnings go through an injected callback: cloud.ts passes logger.warn (stderr, so it survives --json), the MCP tool passes logStderr since its stdout is the JSON-RPC channel.

Also fixes two test fixtures that used a tags: key the CLI never read.

Verified on dev: the bare-list form now exits 1 before anything is submitted, and a well-formed executionOrder chains depends_on null -> 36962 -> 36963 across results 36962-36964.

Fixes #110

What & why

Type of change

  • fix — bug fix
  • feat — new feature
  • perf — performance improvement
  • refactor — code change that's neither a fix nor a feature
  • docs — documentation only
  • chore / ci / build / test — tooling, no user-facing change
  • Breaking change (title has ! or PR notes a BREAKING CHANGE:)

Checklist

  • PR title follows the Conventional Commits format (see comment above)
  • pnpm lint passes
  • pnpm typecheck passes
  • pnpm build passes
  • I have not bumped the version or edited CHANGELOG.md (release-please handles this)
  • I have signed the CLA (the bot will prompt on first contribution)
  • Docs / README.md / STYLE_GUIDE.md updated if behaviour or output changed

How to test


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.

…ng in parallel

A workspace config.yaml was yaml.load'ed and straight-cast to
IWorkspaceConfig, so a wrong-shaped executionOrder was never checked. The
intuitive bare-list form made executionOrder an Array, .flowsOrder came back
undefined, resolveSequentialFlows returned [], and every flow ran in
parallel - same cost, wrong semantics, green run. The only symptom was
depends_on being null on every result row.

Add a zod schema as the single source of truth for the config shape
(src/services/workspace-config.schema.ts) and route all three former cast
sites through one validated loader, loadWorkspaceConfig:

- A malformed executionOrder is now fatal (exit 1), with a message showing
  what was found next to the expected shape. A bare list is not valid
  Maestro either, so there is nothing to accept - and a warning in CI logs
  is exactly what got missed.
- Unrecognised top-level keys warn (and are preserved, since the config is
  forwarded to the API as fields.workspaceConfig), catching flowOrder, a
  top-level continueOnFailure, tags in place of includeTags, and flowTimeout.
- executionOrder on a single-file input warns instead of being dropped:
  planSingleFile never sequences, so it was silently ignored even when
  well-formed.
- continueOnFailure's real default (true) now lives in the schema instead of
  being re-specified at three read sites.
- WORKSPACE_CONFIG_KEYS is derived from the schema so isWorkspaceConfigFile's
  detection set can no longer drift from it.
- includeTags/excludeTags scalar coercion moves from readYamlFileAsJson into
  the schema, so the loader is a plain YAML read and the validator is pure.

Warnings go through an injected callback: cloud.ts passes logger.warn (stderr,
so it survives --json), the MCP tool passes logStderr since its stdout is the
JSON-RPC channel.

Also fixes two test fixtures that used a tags: key the CLI never read.

Verified on dev: the bare-list form now exits 1 before anything is submitted,
and a well-formed executionOrder chains depends_on null -> 36962 -> 36963
across results 36962-36964.

Fixes #110
@finalerock44 finalerock44 self-assigned this Aug 13, 2026
@finalerock44
finalerock44 merged commit 1973a42 into dev Aug 13, 2026
11 checks passed
@finalerock44
finalerock44 deleted the fix/110-workspace-config-validation branch August 13, 2026 09:51
@finalerock44 finalerock44 mentioned this pull request Aug 14, 2026
14 tasks
finalerock44 added a commit that referenced this pull request Aug 14, 2026
Promote from dev to stable:

* fix(cloud): exclude config-shaped files from flow discovery (#114, closes
  dcd-cli#99) — a `config.yaml` sitting in a flows folder was picked up as a
  flow and blew up `processDependencies` with "Expected an array of steps".
  Detection is by shape, not filename, so several named configs can coexist in
  one folder. A flow merely *missing* its `---` separator still errors loudly
  rather than being silently dropped.
* fix(cloud): reject malformed executionOrder instead of silently running in
  parallel (#117, closes dcd-cli#110) — the config was `yaml.load`ed and
  straight-cast, so an `executionOrder` in the wrong shape was ignored and every
  flow ran in parallel. A zod schema (`src/services/workspace-config.schema.ts`)
  is now the single source of truth, with `IWorkspaceConfig` inferred from it so
  the compile-time and runtime views cannot drift.
* deps: bump the minor-and-patch group with 5 updates (#112), plus
  eslint-plugin-unicorn 72 -> 73 and pnpm/action-setup 6.0.9 -> 6.0.10.

Behaviour changes users will notice:

* A malformed `executionOrder` is now a hard error with a targeted message
  showing found-vs-expected. Anyone whose config was mis-shaped has been running
  flows in parallel without knowing; after this they get a clear failure instead.
  That is the point of the fix, but it is the one change that can turn a
  previously-green pipeline red.
* Unrecognised top-level config keys emit a warning (with a did-you-mean for
  near-misses). Keys are preserved, not stripped — the config is forwarded to
  the API as `fields.workspaceConfig`, so stripping would silently alter the
  payload.
* Config warnings go to stderr, so `--json` stdout stays parseable and the MCP
  server's JSON-RPC stdout channel stays clean.

No platform prerequisite: both fixes are client-side (flow discovery and config
validation). The submitted payload for an already-valid config is unchanged.

Carries only the source delta — package.json version, CHANGELOG.md and the
release-please manifests stay as release-please left them on production.

Release-As: 5.3.1
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.

Malformed executionOrder is silently ignored — sequential flows run in parallel with no warning

1 participant