Workflow Validation
Design and behavior of the workflow validation system in Eddy
Workflow Validation
Source: This page is based on docs/WORKFLOW_VALIDATION.md
Last Updated: January 1, 2026
This document outlines the design and behavior of the workflow validation system in Eddy. The purpose of this system is to analyze a workflow's structure and configuration against a central set of rules. This ensures the workflow is logically sound, complete, and runnable before a user attempts to publish or run it.
The validation process is driven by a declarative, data-driven engine that evaluates the entire workflow—including its pages, sections, and blocks—against a predefined rule set. Errors and warnings are aggregated as structured objects and presented to the user in the UI.
Core Architecture: A Declarative, Data-Driven Engine
The validation system is built on the principle of separating the "what" (the validation rules) from the "how" (the engine that runs the validation). This makes the system highly maintainable, readable, and easy to extend.
1. The ValidationError Object
Instead of simple string-based messages, all validation functions produce a consistent, structured ValidationError object. This allows the UI to programmatically handle the results, distinguish between severity levels, and provide targeted, interactive feedback.
The structure, defined in app/types/error.ts, is as follows:
export type WorkflowValidationErrorT = {
// A unique, machine-readable code for the error type
// e.g., 'PAGE_NO_SECTIONS', 'BLOCK_MISSING_BINDING'
code: string
// The human-readable message to be displayed to the user
message: string
// The severity level, allowing the UI to differentiate between
// issues that block publishing ('error') and those that don't ('warning')
level: 'error' | 'warning'
// Metadata about the entity that failed validation
entity: {
type: 'workflow' | 'page' | 'pageTransition' | 'section' | 'block'
id: string
name?: string
}
// Optional field for additional context or data for debugging
details?: Record<string, any>
}2. The Declarative Rule Set
All validation logic is defined as a structured configuration object in app/constants/workflowValidationRules.ts. This object categorizes rules by the entity they apply to (workflow, page, section, block). Each rule definition contains everything needed to perform the check and generate a ValidationError if it fails.
This approach centralizes the business logic, making it easy to get a high-level overview of all system validations and add new rules with minimal boilerplate.
Example of the rule structure:
// app/constants/workflowValidationRules.ts
export const validationRules = {
page: [
{
code: 'PAGE_NO_SECTIONS',
level: 'error',
// The message can be a dynamic function for context-specific feedback
message: (entity: any) => `Stage "${entity.title}" has no sections`,
// The check function returns true if valid, false if invalid
check: (entity: any, context: CheckContext) =>
context.sections.filter(s => s.page_id === entity.id).length > 0
},
{
code: 'PAGE_NO_TRANSITIONS',
level: 'warning',
message: (entity: any) => `Stage "${entity.title}" has no transitions`,
// The 'when' clause makes the rule conditional
when: (entity: any, context: CheckContext) => context.pages.length > 1,
check: (entity: any, context: CheckContext) =>
context.pageTransitions.some(
pt => pt.sourceId === entity.id || pt.targetId === entity.id
)
}
]
}3. The Validation Engine
The validation engine (app/util/validateWorkflowNew.ts) is a generic function that:
- Accepts the workflow data and the rule set
- Iterates through each entity type (workflow, pages, sections, blocks)
- For each entity, runs all applicable rules
- Collects and returns all
ValidationErrorobjects
This separation means adding a new validation rule is as simple as adding a new entry to the rule configuration—no changes to the engine are required.
Current Validation Rules
1. Workflow-Level Validation
| Code | Level | Description |
|---|---|---|
WORKFLOW_NO_PAGES | error | Workflow must have at least one page |
WORKFLOW_NO_START_PAGE | error | Workflow must have exactly one starting page |
WORKFLOW_MULTIPLE_START_PAGES | error | Workflow cannot have multiple starting pages |
WORKFLOW_NO_END_PAGE | warning | Workflow should have at least one end page |
2. Page-Level Validation
| Code | Level | Description |
|---|---|---|
PAGE_NO_SECTIONS | error | Page must have at least one section |
PAGE_NO_TRANSITIONS | warning | Page should have transitions (if workflow has multiple pages) |
PAGE_NO_ROLE_ASSIGNMENT | warning | Page should have at least one role assigned |
PAGE_UNREACHABLE | error | Page is not reachable from the start page |
3. Section-Level Validation
| Code | Level | Description |
|---|---|---|
SECTION_NO_BLOCKS | error | Section must have at least one block |
SECTION_SHEET_NO_SHEET_ID | error | Sheet section must reference a sheet |
4. Block-Level Validation
| Code | Level | Description |
|---|---|---|
BLOCK_NO_LABEL | warning | Block should have a label |
BLOCK_INPUT_NO_BINDING | error | Input block must be bound to a sheet column |
BLOCK_BINDING_INVALID_SHEET | error | Block's sheet binding doesn't match workflow scaffold |
BLOCK_BINDING_COLUMN_NOT_FOUND | error | Block's column binding references non-existent column |
UI Integration
The validation system is tightly integrated with the workflow builder UI:
Validation Panel
A dedicated validation panel displays all errors and warnings:
- Errors (red) - Block publishing and must be fixed
- Warnings (yellow) - Don't block publishing but should be addressed
Interactive Navigation
Clicking on a validation error in the panel:
- Navigates to the problematic entity in the builder
- Highlights the entity
- Opens relevant configuration panels
Real-Time Validation
The validation runs automatically:
- When the workflow is loaded
- When any entity is created, updated, or deleted
- Before publishing
Publishing Guard
The "Publish" button is disabled when there are validation errors, with a tooltip explaining why.
Identified Gaps and Future Enhancements
1. Graph & Connectivity Validation
Current State: We validate that pages have transitions and that there's a start page, but we don't validate the overall graph structure.
Gaps:
- Unreachable Pages: Pages that can never be reached from the start page
- Dead Ends: Pages with no outgoing transitions (except end pages)
- Circular Dependencies: Loops that could trap users indefinitely
Proposed Enhancement: Implement graph traversal algorithms to:
- Detect unreachable pages using BFS/DFS from the start page
- Identify pages with no outgoing transitions that aren't marked as end pages
- Detect problematic loops (optional, as some loops are intentional)
2. Data Binding & Sheet Integrity
Current State: We validate that input blocks are bound to columns, but we don't validate the relationship between block types and column types.
Gaps:
- Type Mismatch: A
dateblock bound to atextcolumn - Missing Columns: Blocks referencing columns that have been deleted
- Orphaned Columns: Columns in the scaffold sheet that aren't used by any blocks
Proposed Enhancement:
- Validate that block types match their bound column types
- Warn when columns exist but aren't used
- Provide migration tools when column types change
3. Conditional Rule Validation
Current State: Rules (conditions) on transitions and sections are not validated.
Gaps:
- Invalid Field References: Rules referencing columns that don't exist
- Type Mismatches: Comparing incompatible types (e.g.,
textfield with>operator) - Circular Dependencies: Rules that reference fields that haven't been filled yet
Proposed Enhancement:
- Parse and validate all rule expressions
- Check that referenced fields exist and are accessible
- Validate operators are appropriate for field types
- Detect circular dependencies in rule chains
4. Block-Specific Content & Configuration
Current State: We validate presence of labels and bindings, but not block-specific configuration.
Gaps:
- Content Blocks: No validation that content blocks have actual content
- Select Blocks: No validation that select blocks have options defined
- File Upload Blocks: No validation of file type restrictions
- Sheet Section Blocks: No validation that the referenced sheet exists
Proposed Enhancement: Add block-type-specific validation rules for:
- Required configuration fields
- Valid option formats
- Content completeness
5. Role Assignment Integrity
Current State: We warn when pages have no role assignments, but we don't validate the overall role system.
Gaps:
- Unused Roles: Roles defined but never assigned to any page
- Incomplete Coverage: Workflows where some pages have role assignments and others don't
- Permission Conflicts: Role permissions that might create workflow deadlocks
Proposed Enhancement:
- Detect unused roles
- Validate consistent role assignment patterns
- Check for permission conflicts that could block progression
6. Gaps Identified from Import/Export Logic
Current State: The import/export system has its own validation that could be integrated.
Gaps:
- Duplicate Names: Multiple pages or roles with the same name
- Invalid References: Cross-references that don't resolve
- Schema Violations: Data that doesn't match expected formats
Proposed Enhancement:
- Integrate import validation rules into the main validation system
- Provide clear error messages for import failures
- Offer automatic fixes for common issues
A Note on Complex Validations: A Hybrid Model
Some validations are too complex or context-dependent to fit into the declarative rule model. For these cases, we use a hybrid approach:
- Declarative Rules: For straightforward, entity-level checks
- Imperative Functions: For complex graph algorithms, cross-entity validation, and performance-sensitive checks
The key is to keep the declarative rules as the primary mechanism and only fall back to imperative code when necessary.
Adding a New Validation Rule
To add a new validation rule:
- Define the rule in
app/constants/workflowValidationRules.ts:
{
code: 'MY_NEW_RULE',
level: 'error', // or 'warning'
message: (entity) => `Entity "${entity.name}" has a problem`,
check: (entity, context) => {
// Return true if valid, false if invalid
return someCondition
},
when: (entity, context) => {
// Optional: only run this rule when this condition is true
return someCondition
}
}-
Test the rule - Add unit tests in
app/__tests__/utils/validateWorkflowNew.test.ts -
Update UI - Ensure the validation panel displays the new error appropriately