Eddy Dev Handbook
Technical reference

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:

  1. Accepts the workflow data and the rule set
  2. Iterates through each entity type (workflow, pages, sections, blocks)
  3. For each entity, runs all applicable rules
  4. Collects and returns all ValidationError objects

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

CodeLevelDescription
WORKFLOW_NO_PAGESerrorWorkflow must have at least one page
WORKFLOW_NO_START_PAGEerrorWorkflow must have exactly one starting page
WORKFLOW_MULTIPLE_START_PAGESerrorWorkflow cannot have multiple starting pages
WORKFLOW_NO_END_PAGEwarningWorkflow should have at least one end page

2. Page-Level Validation

CodeLevelDescription
PAGE_NO_SECTIONSerrorPage must have at least one section
PAGE_NO_TRANSITIONSwarningPage should have transitions (if workflow has multiple pages)
PAGE_NO_ROLE_ASSIGNMENTwarningPage should have at least one role assigned
PAGE_UNREACHABLEerrorPage is not reachable from the start page

3. Section-Level Validation

CodeLevelDescription
SECTION_NO_BLOCKSerrorSection must have at least one block
SECTION_SHEET_NO_SHEET_IDerrorSheet section must reference a sheet

4. Block-Level Validation

CodeLevelDescription
BLOCK_NO_LABELwarningBlock should have a label
BLOCK_INPUT_NO_BINDINGerrorInput block must be bound to a sheet column
BLOCK_BINDING_INVALID_SHEETerrorBlock's sheet binding doesn't match workflow scaffold
BLOCK_BINDING_COLUMN_NOT_FOUNDerrorBlock'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 date block bound to a text column
  • 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., text field 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:

  1. Declarative Rules: For straightforward, entity-level checks
  2. 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:

  1. 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
  }
}
  1. Test the rule - Add unit tests in app/__tests__/utils/validateWorkflowNew.test.ts

  2. Update UI - Ensure the validation panel displays the new error appropriately


On this page