@ema.co/mcp-toolkit 2026.1.27 → 2026.1.28-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.

Files changed (35) hide show
  1. package/dist/mcp/handlers/data/index.js +3 -0
  2. package/dist/mcp/handlers/persona/create.js +16 -0
  3. package/dist/mcp/handlers/persona/list.js +9 -4
  4. package/dist/mcp/handlers/persona/update.js +24 -2
  5. package/dist/mcp/handlers/workflow/deploy.js +20 -2
  6. package/dist/mcp/handlers/workflow/generate.js +39 -2
  7. package/dist/mcp/handlers/workflow/index.js +8 -3
  8. package/dist/mcp/handlers/workflow/modify.js +34 -7
  9. package/dist/mcp/handlers/workflow/validate.js +85 -0
  10. package/dist/mcp/handlers/workflow/validation.js +160 -0
  11. package/dist/mcp/resources.js +286 -4
  12. package/dist/mcp/server.js +16 -3
  13. package/dist/mcp/tools.js +32 -11
  14. package/dist/sdk/client.js +36 -9
  15. package/dist/sdk/ema-client.js +32 -4
  16. package/dist/sdk/index.js +3 -1
  17. package/dist/sdk/knowledge.js +5 -5
  18. package/dist/sdk/structural-rules.js +498 -0
  19. package/dist/sdk/workflow-generator.js +2 -1
  20. package/dist/sdk/workflow-intent.js +28 -96
  21. package/dist/sdk/workflow-path-enumerator.js +278 -0
  22. package/dist/sdk/workflow-static-validator.js +291 -0
  23. package/dist/sdk/workflow-validation-types.js +7 -0
  24. package/docs/README.md +14 -0
  25. package/docs/go-validator-analysis.md +323 -0
  26. package/docs/rule-format-specification.md +346 -0
  27. package/docs/validation-contract.md +397 -0
  28. package/docs/validation-error-format.md +326 -0
  29. package/package.json +1 -1
  30. package/dist/mcp/workflow-operations.js +0 -100
  31. package/dist/sdk/workflow-fixer.js +0 -48
  32. package/docs/dashboard-operations.md +0 -281
  33. package/docs/ema-user-guide.md +0 -1201
  34. package/docs/email-patterns.md +0 -120
  35. package/docs/mcp-tools-guide.md +0 -575
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Static Workflow Validator
3
+ *
4
+ * Implements Go validator's static validation logic:
5
+ * - Path enumeration
6
+ * - Required output validation (all paths produce all named results)
7
+ * - Multiple writers detection
8
+ * - Response/abstain validation
9
+ * - Category hierarchy validation
10
+ *
11
+ * Based on: workflow-engine/pkg/engine/validator.go
12
+ */
13
+ import { enumeratePaths } from "./workflow-path-enumerator.js";
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ // Main Validation Function
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+ /**
18
+ * Perform static validation on a workflow.
19
+ *
20
+ * Only validates workflows with named results (resultMappings).
21
+ * If no named results, returns valid (matching Go validator behavior).
22
+ */
23
+ export function validateWorkflowStatic(workflow, options) {
24
+ // Collect all errors (path-dependent and global)
25
+ const errors = [];
26
+ const warnings = [];
27
+ const info = [];
28
+ // Validate categorizers globally (not dependent on named results)
29
+ const categorizerErrors = validateAllCategorizers(workflow);
30
+ errors.push(...categorizerErrors.filter(e => e.severity === "critical"));
31
+ warnings.push(...categorizerErrors.filter(e => e.severity === "warning"));
32
+ info.push(...categorizerErrors.filter(e => e.severity === "info"));
33
+ // Static validation only applies to workflows with named results
34
+ if (!workflow.resultMappings || workflow.resultMappings.length === 0) {
35
+ // Count errors by type
36
+ const errorsByType = {
37
+ required_output_not_produced: 0,
38
+ multiple_writers: 0,
39
+ missing_response_or_abstain_reason: 0,
40
+ category_hierarchy_violation: errors.filter(e => e.type === "category_hierarchy_violation").length,
41
+ };
42
+ return {
43
+ valid: errors.length === 0,
44
+ errors,
45
+ warnings,
46
+ info,
47
+ summary: {
48
+ total_paths: 0,
49
+ valid_paths: 0,
50
+ invalid_paths: 0,
51
+ errors_by_type: errorsByType,
52
+ },
53
+ };
54
+ }
55
+ // Enumerate all execution paths
56
+ const paths = enumeratePaths(workflow, options);
57
+ // Extract named results from resultMappings
58
+ const namedResults = extractNamedResults(workflow.resultMappings);
59
+ // Validate each path
60
+ for (const path of paths) {
61
+ const pathErrors = validatePath(path, workflow, namedResults);
62
+ errors.push(...pathErrors.filter(e => e.severity === "critical"));
63
+ warnings.push(...pathErrors.filter(e => e.severity === "warning"));
64
+ info.push(...pathErrors.filter(e => e.severity === "info"));
65
+ }
66
+ // Count errors by type
67
+ const errorsByType = {
68
+ required_output_not_produced: errors.filter(e => e.type === "required_output_not_produced").length,
69
+ multiple_writers: errors.filter(e => e.type === "multiple_writers").length,
70
+ missing_response_or_abstain_reason: errors.filter(e => e.type === "missing_response_or_abstain_reason").length,
71
+ category_hierarchy_violation: errors.filter(e => e.type === "category_hierarchy_violation").length,
72
+ };
73
+ const validPaths = paths.filter(p => !p.validation_failed).length;
74
+ const invalidPaths = paths.filter(p => p.validation_failed).length;
75
+ return {
76
+ valid: errors.length === 0,
77
+ errors,
78
+ warnings,
79
+ info,
80
+ summary: {
81
+ total_paths: paths.length,
82
+ valid_paths: validPaths,
83
+ invalid_paths: invalidPaths,
84
+ errors_by_type: errorsByType,
85
+ },
86
+ };
87
+ }
88
+ // ─────────────────────────────────────────────────────────────────────────────
89
+ // Path Validation
90
+ // ─────────────────────────────────────────────────────────────────────────────
91
+ function validatePath(path, workflow, namedResults) {
92
+ const errors = [];
93
+ // 1. Check required outputs
94
+ errors.push(...validateRequiredOutputs(path, workflow, namedResults));
95
+ // 2. Check multiple writers
96
+ errors.push(...validateMultipleWriters(path, workflow, namedResults));
97
+ // 3. Check response/abstain
98
+ errors.push(...validateResponseOrAbstain(path, workflow));
99
+ // 4. Check category hierarchy (if path has categorizer)
100
+ errors.push(...validateCategoryHierarchy(path, workflow));
101
+ return errors;
102
+ }
103
+ // ─────────────────────────────────────────────────────────────────────────────
104
+ // Validation: Required Outputs
105
+ // ─────────────────────────────────────────────────────────────────────────────
106
+ function validateRequiredOutputs(path, workflow, namedResults) {
107
+ const errors = [];
108
+ // Get outputs produced on this path
109
+ const producedOutputs = new Set(Object.keys(path.final_outputs));
110
+ // Check each named result
111
+ for (const namedResult of namedResults) {
112
+ if (!producedOutputs.has(namedResult)) {
113
+ // Extract node and output from named result format: "nodeId.outputName"
114
+ const [nodeId, outputName] = namedResult.split(".");
115
+ errors.push({
116
+ type: "required_output_not_produced",
117
+ rule_id: "required_output_all_paths",
118
+ severity: "critical",
119
+ location: {
120
+ path: path.completed_actions,
121
+ category: path.chosen_enumerable_value,
122
+ named_result: namedResult,
123
+ action_name: path.action_name,
124
+ output_name: path.enumerable_output,
125
+ output_value: path.chosen_enumerable_value,
126
+ },
127
+ what: `Path '${formatPathName(path)}' does not produce required output '${namedResult}'`,
128
+ why: `All execution paths from trigger must produce all named results. The path '${formatPathName(path)}' ends at '${path.action_name}' without producing '${namedResult}'.`,
129
+ how_to_fix: `Add a node that produces '${namedResult}' to the '${formatPathName(path)}' path. Connect the node's '${outputName}' output to WORKFLOW_OUTPUT via resultMappings.`,
130
+ rule_reference: "ema://validation/rules#required_output_all_paths",
131
+ examples: {
132
+ bad: `trigger → ${path.completed_actions.join(" → ")} → (no ${outputName})`,
133
+ good: `trigger → ${path.completed_actions.join(" → ")} → ${nodeId} → WORKFLOW_OUTPUT`,
134
+ },
135
+ });
136
+ }
137
+ }
138
+ return errors;
139
+ }
140
+ // ─────────────────────────────────────────────────────────────────────────────
141
+ // Validation: Multiple Writers
142
+ // ─────────────────────────────────────────────────────────────────────────────
143
+ function validateMultipleWriters(path, workflow, namedResults) {
144
+ const errors = [];
145
+ // Build map: namedResult -> nodes that produce it
146
+ const producers = new Map();
147
+ for (const namedResult of namedResults) {
148
+ const [nodeId] = namedResult.split(".");
149
+ // Check if this node is in the path
150
+ if (path.completed_actions.includes(nodeId)) {
151
+ if (!producers.has(namedResult)) {
152
+ producers.set(namedResult, []);
153
+ }
154
+ producers.get(namedResult).push(nodeId);
155
+ }
156
+ }
157
+ // Check for multiple producers
158
+ for (const [namedResult, nodeIds] of producers.entries()) {
159
+ if (nodeIds.length > 1) {
160
+ errors.push({
161
+ type: "multiple_writers",
162
+ rule_id: "single_writer_per_output",
163
+ severity: "critical",
164
+ location: {
165
+ path: path.completed_actions,
166
+ category: path.chosen_enumerable_value,
167
+ named_result: namedResult,
168
+ action_name: path.action_name,
169
+ },
170
+ what: `Multiple nodes produce '${namedResult}' on path '${formatPathName(path)}'`,
171
+ why: `A single named result can only have one producer per execution path. The path '${formatPathName(path)}' has both '${nodeIds[0]}' and '${nodeIds[1]}' producing '${namedResult}'.`,
172
+ how_to_fix: `Ensure only one node produces '${namedResult}' on the '${formatPathName(path)}' path. Either remove one of the producers or use different named results for each node.`,
173
+ rule_reference: "ema://validation/rules#single_writer_per_output",
174
+ });
175
+ }
176
+ }
177
+ return errors;
178
+ }
179
+ // ─────────────────────────────────────────────────────────────────────────────
180
+ // Validation: Response or Abstain
181
+ // ─────────────────────────────────────────────────────────────────────────────
182
+ function validateResponseOrAbstain(path, workflow) {
183
+ const errors = [];
184
+ // Check if path produces any response
185
+ const responseNodes = ["respond_with_sources", "call_llm", "fixed_response"];
186
+ const hasResponse = path.completed_actions.some(actionId => {
187
+ const node = workflow.nodes.find(n => n.id === actionId);
188
+ return node && responseNodes.includes(node.actionType);
189
+ });
190
+ // Check if path has abstain reason
191
+ // TODO: How is abstain reason defined? Need to check Go code
192
+ const hasAbstain = false; // Placeholder
193
+ if (!hasResponse && !hasAbstain) {
194
+ errors.push({
195
+ type: "missing_response_or_abstain_reason",
196
+ rule_id: "response_or_abstain_required",
197
+ severity: "critical",
198
+ location: {
199
+ path: path.completed_actions,
200
+ category: path.chosen_enumerable_value,
201
+ action_name: path.action_name,
202
+ },
203
+ what: `Path '${formatPathName(path)}' does not produce a response and has no abstain reason`,
204
+ why: `Every execution path must either produce a user-visible response or explicitly abstain with a reason. The path '${formatPathName(path)}' ends at '${path.action_name}' without a response node and no abstain reason is specified.`,
205
+ how_to_fix: `Either add a response node (respond_with_sources, call_llm, or fixed_response) to the '${formatPathName(path)}' path, or add an abstain reason explaining why this path doesn't respond.`,
206
+ rule_reference: "ema://validation/rules#response_or_abstain_required",
207
+ });
208
+ }
209
+ return errors;
210
+ }
211
+ // ─────────────────────────────────────────────────────────────────────────────
212
+ // Validation: Category Hierarchy
213
+ // ─────────────────────────────────────────────────────────────────────────────
214
+ function validateCategoryHierarchy(path, workflow) {
215
+ const errors = [];
216
+ // Find categorizer nodes in path
217
+ const categorizers = path.completed_actions
218
+ .map(actionId => workflow.nodes.find(n => n.id === actionId))
219
+ .filter((node) => node !== undefined &&
220
+ (node.actionType === "chat_categorizer" || node.actionType === "text_categorizer"));
221
+ for (const categorizer of categorizers) {
222
+ // Check if categorizer has Fallback category
223
+ const hasFallback = categorizer.categories?.some(cat => cat.name === "Fallback" || cat.name === "Other");
224
+ if (!hasFallback) {
225
+ errors.push({
226
+ type: "category_hierarchy_violation",
227
+ rule_id: "category_hierarchy_valid",
228
+ severity: "critical",
229
+ location: {
230
+ node_id: categorizer.id,
231
+ path: path.completed_actions,
232
+ },
233
+ what: `Categorizer '${categorizer.id}' does not have a Fallback category`,
234
+ why: "Every categorizer must have a Fallback category to handle unrecognized intents. Without it, some user queries will have no handler.",
235
+ how_to_fix: `Add a Fallback category to the categorizer '${categorizer.id}' with description 'For unclear or other requests'.`,
236
+ rule_reference: "ema://validation/rules#category_hierarchy_valid",
237
+ });
238
+ }
239
+ // TODO: Check for category hierarchy violations
240
+ // Need to analyze named_result_agent_assist_validator.go for exact rules
241
+ }
242
+ return errors;
243
+ }
244
+ // ─────────────────────────────────────────────────────────────────────────────
245
+ // Helper Functions
246
+ // ─────────────────────────────────────────────────────────────────────────────
247
+ function extractNamedResults(resultMappings) {
248
+ const namedResults = new Set();
249
+ for (const mapping of resultMappings) {
250
+ const namedResult = `${mapping.nodeId}.${mapping.output}`;
251
+ namedResults.add(namedResult);
252
+ }
253
+ return namedResults;
254
+ }
255
+ function formatPathName(path) {
256
+ if (path.chosen_enumerable_value) {
257
+ return path.chosen_enumerable_value;
258
+ }
259
+ if (path.action_name) {
260
+ return path.action_name;
261
+ }
262
+ return "Unknown";
263
+ }
264
+ // ─────────────────────────────────────────────────────────────────────────────
265
+ // Global Categorizer Validation (not path-dependent)
266
+ // ─────────────────────────────────────────────────────────────────────────────
267
+ function validateAllCategorizers(workflow) {
268
+ const errors = [];
269
+ // Find all categorizer nodes in workflow
270
+ const categorizers = workflow.nodes.filter(node => node.actionType === "chat_categorizer" ||
271
+ node.actionType === "text_categorizer");
272
+ for (const categorizer of categorizers) {
273
+ // Check if categorizer has Fallback category
274
+ const hasFallback = categorizer.categories?.some(cat => cat.name === "Fallback" || cat.name === "Other");
275
+ if (!hasFallback) {
276
+ errors.push({
277
+ type: "category_hierarchy_violation",
278
+ rule_id: "category_hierarchy_valid",
279
+ severity: "critical",
280
+ location: {
281
+ node_id: categorizer.id,
282
+ },
283
+ what: `Categorizer '${categorizer.id}' does not have a Fallback category`,
284
+ why: "Every categorizer must have a Fallback category to handle unrecognized intents. Without it, some user queries will have no handler.",
285
+ how_to_fix: `Add a Fallback category to the categorizer '${categorizer.id}' with description 'For unclear or other requests'.`,
286
+ rule_reference: "ema://validation/rules#category_hierarchy_valid",
287
+ });
288
+ }
289
+ }
290
+ return errors;
291
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Validation Types - Enhanced Error Format
3
+ *
4
+ * Defines TypeScript interfaces for validation errors with enhanced
5
+ * "what, why, how" format for clear, actionable error messages.
6
+ */
7
+ export {};
package/docs/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # Deprecated
2
+
3
+ > **This directory is deprecated.** Public documentation has moved to `.context/public/guides/`.
4
+
5
+ ## Moved files
6
+
7
+ - `ema-user-guide.md` → `.context/public/guides/ema-user-guide.md`
8
+ - `mcp-tools-guide.md` → `.context/public/guides/mcp-tools-guide.md`
9
+ - `dashboard-operations.md` → `.context/public/guides/dashboard-operations.md`
10
+ - `email-patterns.md` → `.context/public/guides/email-patterns.md`
11
+
12
+ ## Future
13
+
14
+ This directory may be re-added as a symlink to internal docs once cutover is verified.
@@ -0,0 +1,323 @@
1
+ # Go Validator Analysis
2
+
3
+ **Date**: 2026-01-27
4
+ **Source**: `workflow-engine/pkg/engine/validator.go`
5
+ **Purpose**: Detailed analysis of Go validator implementation
6
+
7
+ ---
8
+
9
+ ## Architecture Overview
10
+
11
+ ### Components
12
+
13
+ 1. **Validator Interface**: `Validator.Validate(ctx, workflow)`
14
+ 2. **Local Validator**: Implements validator interface
15
+ 3. **Path Enumeration**: `enumerateAndValidatePaths()` - Core algorithm
16
+ 4. **Static Validation Strategy**: `StaticValidationStrategy` - Simulation strategy
17
+ 5. **Validation Session**: `ValidationWorkflowSession` - Session state management
18
+
19
+ ---
20
+
21
+ ## Core Algorithm: Path Enumeration
22
+
23
+ ### Algorithm Flow
24
+
25
+ ```
26
+ 1. Create initial validation session
27
+ └─> Dummy inputs, empty state
28
+
29
+ 2. Queue initial session
30
+ └─> sessionsToProcess = [initialSession]
31
+
32
+ 3. While queue not empty:
33
+ a. Dequeue session
34
+ b. Run validation on session
35
+ └─> session.RunValidation(ctx)
36
+
37
+ c. Handle result:
38
+ ├─> OutcomeBranch:
39
+ │ └─> For each possible value:
40
+ │ ├─> Fork session
41
+ │ ├─> Plug chosen value
42
+ │ ├─> Mark action complete
43
+ │ └─> Queue forked session
44
+
45
+ └─> OutcomeCompleted:
46
+ ├─> Track completed path
47
+ ├─> Validate named results
48
+ ├─> Apply custom validators
49
+ └─> Store in completedSessionMap
50
+
51
+ 4. Cleanup incomplete sessions
52
+ 5. Return completedSessionMap
53
+ ```
54
+
55
+ ### Key Data Structures
56
+
57
+ #### completedPathInfo
58
+
59
+ ```go
60
+ type completedPathInfo struct {
61
+ CompletedActions []string // Actions executed (topological order)
62
+ FinalOutputs map[string]*pb.Value // Final outputs produced
63
+ ActionName string // Path identifier
64
+ EnumerableOutput *string // Branching output name (if branched)
65
+ ChosenEnumerableValue *string // Chosen value (if branched)
66
+ NamedResultsValidationsFailed bool // Validation failure flag
67
+ }
68
+ ```
69
+
70
+ #### BranchPointInfo
71
+
72
+ ```go
73
+ type BranchPointInfo struct {
74
+ ActionThatBranched *ActionNode // The branching action
75
+ BranchingOutputName string // Output that branches
76
+ PossibleValues []*pb.Value // All possible values
77
+ ChosenValue *pb.Value // Value chosen for this fork
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Static Validation Strategy
84
+
85
+ ### Purpose
86
+
87
+ Simulates workflow execution without actually running actions:
88
+ - Produces dummy outputs for non-branching actions
89
+ - Returns branching information for branching actions
90
+ - Never calls actual action code
91
+
92
+ ### Key Methods
93
+
94
+ #### BindWorkflowInputs
95
+
96
+ - Generates type-aware dummy values for all workflow inputs
97
+ - Binds to input bindings
98
+ - Validates type compatibility
99
+
100
+ #### BindWidgetConfigs
101
+
102
+ - Generates type-aware dummy values for all widget configs
103
+ - Binds to widget config bindings
104
+ - Validates type compatibility
105
+
106
+ #### ExecuteAction
107
+
108
+ **For Branching Actions**:
109
+ - Returns `ActionExecutionResult{Outputs: nil, BranchPoint: &branchPoint}`
110
+ - BranchPoint contains: action, output name, possible values
111
+
112
+ **For Non-Branching Actions**:
113
+ - Generates type-aware dummy outputs for all outputs
114
+ - Marks action as completed
115
+ - Returns `ActionExecutionResult{Outputs: outputs}`
116
+
117
+ ### Enumerable Output Detection
118
+
119
+ ```go
120
+ func isOutputTypeEnumerable(outputDef *pb.ActionType_OutputDef) bool {
121
+ if outputDef.GetArgumentType().GetStatic() == nil {
122
+ return false
123
+ }
124
+ return outputDef.GetArgumentType().GetStatic().GetEnumType() != nil ||
125
+ outputDef.GetArgumentType().GetStatic().GetWellKnownType() == pb.WellKnownType_WELL_KNOWN_TYPE_BOOL
126
+ }
127
+ ```
128
+
129
+ **Enumerable Types**:
130
+ - Enum types (categorizer categories)
131
+ - Boolean types (true/false)
132
+
133
+ **Non-Enumerable Types**:
134
+ - Strings, numbers, complex types
135
+ - Unknown types (dynamic)
136
+
137
+ ---
138
+
139
+ ## Path Forking Logic
140
+
141
+ ### forkForBranching Function
142
+
143
+ ```go
144
+ func forkForBranching(
145
+ session ValidationWorkflowSession,
146
+ branchInfo *BranchPointInfo,
147
+ branchValue *pb.Value,
148
+ ) (ValidationWorkflowSession, error)
149
+ ```
150
+
151
+ **Steps**:
152
+ 1. Deep copy session
153
+ 2. Create fake outputs for branching action:
154
+ - For branching output: Use chosen value
155
+ - For other outputs: Generate type-aware dummy values
156
+ 3. Merge fake outputs into session
157
+ 4. Mark branching action as completed
158
+ 5. Update last encountered branching action in session
159
+ 6. Return forked session
160
+
161
+ ### Session State Management
162
+
163
+ - Each forked session is independent
164
+ - State includes: completed actions, available outputs, action states
165
+ - Forking preserves parent state, then modifies for chosen branch
166
+
167
+ ---
168
+
169
+ ## Named Results Validation
170
+
171
+ ### Validation Points
172
+
173
+ 1. **Required Output Check**: All named results produced on all paths
174
+ 2. **Multiple Writers Check**: No duplicate producers on same path
175
+ 3. **Response/Abstain Check**: Every path has response or abstain
176
+ 4. **Category Hierarchy Check**: Categorizer structure valid
177
+
178
+ ### Where Validation Happens
179
+
180
+ Validation occurs in `local_executor.go` during path completion:
181
+ - `AddNamedResultError()` called when validation fails
182
+ - Errors stored in workflow graph
183
+ - Workflow marked as non-runnable if errors exist
184
+
185
+ ---
186
+
187
+ ## Error Reporting
188
+
189
+ ### Error Storage
190
+
191
+ Errors stored in workflow graph:
192
+ ```go
193
+ workflow.AddNamedResultError(errorType, namedResultName, pathIdentifier, message)
194
+ ```
195
+
196
+ ### Error Structure
197
+
198
+ ```go
199
+ type WorkflowError_NamedResultError struct {
200
+ ErrorType pb.WorkflowError_ResultErrorType
201
+ NamedResultName string
202
+ PathIdentifier *WorkflowError_ActionOutput
203
+ Message string
204
+ }
205
+ ```
206
+
207
+ ### Path Identifier Format
208
+
209
+ ```go
210
+ type WorkflowError_ActionOutput struct {
211
+ ActionName string // Always present
212
+ OutputName string // Only for branching paths
213
+ OutputValue string // Only for branching paths
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ## Custom Validators
220
+
221
+ ### Interface
222
+
223
+ ```go
224
+ type CustomNamedResultValidator interface {
225
+ Validate(workflow *WorkflowGraph, pathInfo *completedPathInfo) error
226
+ }
227
+ ```
228
+
229
+ ### Implementation Example
230
+
231
+ `named_result_agent_assist_validator.go`:
232
+ - Validates Agent Assist persona-specific rules
233
+ - Checks category hierarchy
234
+ - Checks response/abstain for Agent Assist patterns
235
+
236
+ ### Integration
237
+
238
+ - Retrieved via `GetCustomValidatorForPersonaType()`
239
+ - Called after general validations pass
240
+ - Can add persona-specific errors
241
+
242
+ ---
243
+
244
+ ## Performance Characteristics
245
+
246
+ ### Complexity
247
+
248
+ - **Time**: O(P * A) where P = paths, A = actions per path
249
+ - **Space**: O(P * A) for session storage
250
+
251
+ ### Optimization Opportunities
252
+
253
+ 1. **Early Pruning**: Stop if path already invalid
254
+ 2. **Path Caching**: Cache results for unchanged workflows
255
+ 3. **Parallel Enumeration**: Enumerate paths in parallel (future)
256
+
257
+ ---
258
+
259
+ ## Test Coverage
260
+
261
+ ### Test Files
262
+
263
+ - `local_executor_static_validation_test.go` - Core validation tests
264
+ - `local_executor_named_outputs_test.go` - Named results tests
265
+ - `local_enumerator_test.go` - Path enumeration tests
266
+
267
+ ### Test Patterns
268
+
269
+ 1. **Simple workflows**: Linear, no branching
270
+ 2. **Categorizer workflows**: Single categorizer, multiple branches
271
+ 3. **Nested branching**: Multiple categorizers in sequence
272
+ 4. **Error cases**: Missing outputs, multiple writers, etc.
273
+
274
+ ---
275
+
276
+ ## Key Insights
277
+
278
+ ### 1. Dummy Value Generation
279
+
280
+ - All dummy values are **type-aware**
281
+ - Uses `util.CreateTypeAwareDummyValue()` for type compatibility
282
+ - Preserves type information for validation
283
+
284
+ ### 2. Branching Detection
285
+
286
+ - Only enum and boolean outputs are considered enumerable
287
+ - Other types cannot be used for path enumeration
288
+ - Unknown types default to non-branching
289
+
290
+ ### 3. Path Identification
291
+
292
+ - Paths are identified by **last branching action + value** OR **last action**
293
+ - This allows precise error reporting
294
+ - Path identifier used in error messages
295
+
296
+ ### 4. Session Management
297
+
298
+ - Sessions are deep copied when forking
299
+ - Each session maintains independent state
300
+ - Completed sessions stored in map for analysis
301
+
302
+ ---
303
+
304
+ ## Implementation Notes for TypeScript
305
+
306
+ ### Must Implement
307
+
308
+ 1. **Path Enumeration**: Topological sort + DFS + forking
309
+ 2. **Dummy Value Generation**: Type-aware dummy values
310
+ 3. **Branching Detection**: Enum and boolean outputs
311
+ 4. **Session Management**: Deep copying, state tracking
312
+ 5. **Error Reporting**: Same error types and path identifiers
313
+
314
+ ### Can Enhance
315
+
316
+ 1. **Error Messages**: Add what/why/how format
317
+ 2. **Performance**: Add caching, early pruning
318
+ 3. **Additional Validations**: Add more checks (document as extensions)
319
+
320
+ ---
321
+
322
+ **Status**: ✅ Analysis complete
323
+ **Next**: Use this analysis to implement TypeScript validator