@hotmeshio/long-tail 0.5.7 → 0.5.10

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.
@@ -125,10 +125,26 @@ async function resolveByMetadata(input, auth) {
125
125
  if (result.outcome === 'not_found') {
126
126
  return { status: 404, error: 'No pending escalation found for this metadata, or insufficient role permissions' };
127
127
  }
128
+ if (result.outcome === 'conflict') {
129
+ return { status: 409, error: 'A concurrent resolution is already in progress for this escalation' };
130
+ }
128
131
  if (result.outcome === 'resolved') {
129
132
  return { status: 200, data: { escalation: result.escalation } };
130
133
  }
131
- // Signal-backed escalation signal the workflow, conditionLT resolves durably
134
+ // Atomic conditionLT escalation (signal_key set) SDK resolve atomically marks
135
+ // resolved AND delivers the signal to the waiting condition(), resuming the workflow.
136
+ if (result.signalKey) {
137
+ const resolved = await escalationService.resolveEscalation(result.escalationId, input.resolverPayload);
138
+ if (!resolved) {
139
+ return { status: 409, error: 'Escalation not available for resolution' };
140
+ }
141
+ return {
142
+ status: 200,
143
+ data: { signaled: true, escalationId: result.escalationId, workflowId: result.workflowId },
144
+ };
145
+ }
146
+ // Legacy conditionLT escalation (metadata.signal_id set) — signal the workflow,
147
+ // conditionLT interceptor resolves durably via ltResolveEscalation.
132
148
  const { createClient } = await Promise.resolve().then(() => __importStar(require('../../workers')));
133
149
  const client = createClient();
134
150
  const handle = await client.workflow.getHandle(result.taskQueue, result.workflowType, result.workflowId);
@@ -103,12 +103,19 @@ export declare function claimByMetadata(key: string, value: string, userId: stri
103
103
  candidatesExist: number;
104
104
  }) | null>;
105
105
  export interface ResolveByMetadataResult {
106
- /** 'resolved' = done atomically. 'signal_required' = signal_id present, caller must signal. */
107
- outcome: 'resolved' | 'signal_required' | 'not_found';
106
+ /**
107
+ * 'resolved' = done atomically in SQL (no signal backing).
108
+ * 'signal_required' = signal backing present, caller must deliver the signal.
109
+ * 'conflict' = signal_id row already claimed by a concurrent caller; skip re-signal.
110
+ * 'not_found' = no pending escalation matched the metadata filter.
111
+ */
112
+ outcome: 'resolved' | 'signal_required' | 'conflict' | 'not_found';
108
113
  /** The resolved escalation (when outcome = 'resolved') */
109
114
  escalation?: LTEscalationRecord;
110
- /** Signal info (when outcome = 'signal_required') */
115
+ /** Legacy conditionLT signal info (when signalId is set, caller uses handle.signal) */
111
116
  signalId?: string;
117
+ /** Atomic conditionLT signal key (when signalKey is set, caller uses SDK resolve to atomically mark+signal) */
118
+ signalKey?: string;
112
119
  escalationId?: string;
113
120
  workflowId?: string;
114
121
  workflowType?: string;
@@ -402,10 +402,15 @@ async function resolveByMetadataAtomic(key, value, userId, resolverPayload, meta
402
402
  });
403
403
  return { outcome: 'resolved', escalation };
404
404
  }
405
+ // Signal_id row already claimed by a concurrent caller — skip re-signal to prevent duplicate delivery.
406
+ if (row.signal_id && row.signal_already_claimed) {
407
+ return { outcome: 'conflict', escalationId: row.target_id };
408
+ }
405
409
  // Signal-backed escalation — return the signal info for the caller to deliver.
406
410
  return {
407
411
  outcome: 'signal_required',
408
- signalId: row.signal_id,
412
+ signalId: row.signal_id ?? undefined,
413
+ signalKey: row.signal_key ?? undefined,
409
414
  escalationId: row.target_id,
410
415
  workflowId: row.target_workflow_id,
411
416
  workflowType: row.target_workflow_type,
@@ -11,13 +11,23 @@ export declare const RELEASE_EXPIRED_CLAIMS = "UPDATE public.hmsh_escalations\nS
11
11
  /**
12
12
  * Atomic resolve by metadata with signal guard.
13
13
  *
14
- * Single query, two outcomes:
15
- * 1. No `metadata.signal_id` → claim + resolve atomically. `resolved` is populated.
16
- * 2. `metadata.signal_id` present → resolve CTE skips (guard in WHERE). `resolved`
17
- * is null, but `target_id`, `signal_id`, and workflow routing are returned so
18
- * the caller can signal the workflow directly.
14
+ * Single query, four outcomes:
15
+ * 1. No signal backing → claim + resolve atomically. `resolved` is populated.
16
+ * 2. `metadata.signal_id` present, row unclaimed claim the row (preventing concurrent
17
+ * duplicate signals via FOR UPDATE serialization), then return signal info so the
18
+ * caller can signal the workflow. `signal_already_claimed = false`.
19
+ * 3. `metadata.signal_id` present, row already claimed and claim not expired →
20
+ * a concurrent caller is handling the signal. `signal_already_claimed = true`;
21
+ * caller returns 409 without re-signaling.
22
+ * 4. `signal_key` present (atomic conditionLT) → `claimed` and `resolved` both skip.
23
+ * Caller invokes SDK resolve to atomically mark resolved + deliver the signal.
24
+ *
25
+ * Signal_id hardening: the `claimed` CTE runs for signal_id rows (previously excluded).
26
+ * This stamps `assigned_to` on the row inside the FOR UPDATE transaction, so a
27
+ * concurrent second caller sees `signal_already_claimed = true` and aborts.
28
+ * The `resolved` CTE still skips signal_id rows — the workflow resolves durably.
19
29
  *
20
30
  * $1 = metadata filter (jsonb), $2 = userId, $3 = resolver_payload (jsonb),
21
31
  * $4 = metadata patch (jsonb, nullable), $5 = allowed roles (text[], null = no filter)
22
32
  */
23
- export declare const RESOLVE_BY_METADATA_ATOMIC = "WITH target AS MATERIALIZED (\n SELECT *\n FROM public.hmsh_escalations\n WHERE metadata @> $1::jsonb\n AND status = 'pending'\n AND ($5::text[] IS NULL OR role = ANY($5))\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n FOR UPDATE\n),\nclaimed AS (\n UPDATE public.hmsh_escalations e\n SET assigned_to = COALESCE(e.assigned_to, $2),\n claimed_at = COALESCE(e.claimed_at, NOW()),\n assigned_until = CASE\n WHEN e.assigned_to IS NOT NULL AND e.assigned_until > NOW() THEN e.assigned_until\n ELSE NOW() + INTERVAL '5 minutes' END,\n claim_expires_at = CASE\n WHEN e.assigned_to IS NOT NULL AND e.assigned_until > NOW() THEN e.claim_expires_at\n ELSE NOW() + INTERVAL '5 minutes' END,\n metadata = CASE WHEN $4::jsonb IS NOT NULL\n THEN COALESCE(e.metadata, '{}'::jsonb) || $4::jsonb\n ELSE e.metadata END,\n updated_at = NOW()\n FROM target\n WHERE e.id = target.id\n AND (target.metadata->>'signal_id') IS NULL\n RETURNING e.*\n),\nresolved AS (\n UPDATE public.hmsh_escalations e\n SET status = 'resolved',\n resolved_at = NOW(),\n resolver_payload = $3,\n updated_at = NOW()\n FROM claimed\n WHERE e.id = claimed.id\n RETURNING e.*\n)\nSELECT\n resolved.*,\n target.id AS target_id,\n target.metadata->>'signal_id' AS signal_id,\n target.workflow_id AS target_workflow_id,\n target.workflow_type AS target_workflow_type,\n target.task_queue AS target_task_queue,\n CASE WHEN resolved.id IS NOT NULL THEN 'resolved' ELSE 'signal_required' END AS outcome\nFROM target\nLEFT JOIN resolved ON resolved.id = target.id";
33
+ export declare const RESOLVE_BY_METADATA_ATOMIC = "WITH target AS MATERIALIZED (\n SELECT *\n FROM public.hmsh_escalations\n WHERE metadata @> $1::jsonb\n AND status = 'pending'\n AND ($5::text[] IS NULL OR role = ANY($5))\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n FOR UPDATE\n),\nclaimed AS (\n UPDATE public.hmsh_escalations e\n SET assigned_to = COALESCE(e.assigned_to, $2),\n claimed_at = COALESCE(e.claimed_at, NOW()),\n assigned_until = CASE\n WHEN e.assigned_to IS NOT NULL AND e.assigned_until > NOW() THEN e.assigned_until\n ELSE NOW() + INTERVAL '5 minutes' END,\n claim_expires_at = CASE\n WHEN e.assigned_to IS NOT NULL AND e.assigned_until > NOW() THEN e.claim_expires_at\n ELSE NOW() + INTERVAL '5 minutes' END,\n metadata = CASE WHEN $4::jsonb IS NOT NULL\n THEN COALESCE(e.metadata, '{}'::jsonb) || $4::jsonb\n ELSE e.metadata END,\n updated_at = NOW()\n FROM target\n WHERE e.id = target.id\n AND target.signal_key IS NULL\n RETURNING e.*\n),\nresolved AS (\n UPDATE public.hmsh_escalations e\n SET status = 'resolved',\n resolved_at = NOW(),\n resolver_payload = $3,\n updated_at = NOW()\n FROM claimed\n WHERE e.id = claimed.id\n AND (claimed.metadata->>'signal_id') IS NULL\n RETURNING e.*\n)\nSELECT\n resolved.*,\n target.id AS target_id,\n target.metadata->>'signal_id' AS signal_id,\n target.signal_key AS signal_key,\n target.workflow_id AS target_workflow_id,\n target.workflow_type AS target_workflow_type,\n target.task_queue AS target_task_queue,\n (target.assigned_to IS NOT NULL\n AND target.assigned_until IS NOT NULL\n AND target.assigned_until > NOW()) AS signal_already_claimed,\n CASE WHEN resolved.id IS NOT NULL THEN 'resolved' ELSE 'signal_required' END AS outcome\nFROM target\nLEFT JOIN resolved ON resolved.id = target.id";
@@ -34,11 +34,21 @@ WHERE status = 'pending'
34
34
  /**
35
35
  * Atomic resolve by metadata with signal guard.
36
36
  *
37
- * Single query, two outcomes:
38
- * 1. No `metadata.signal_id` → claim + resolve atomically. `resolved` is populated.
39
- * 2. `metadata.signal_id` present → resolve CTE skips (guard in WHERE). `resolved`
40
- * is null, but `target_id`, `signal_id`, and workflow routing are returned so
41
- * the caller can signal the workflow directly.
37
+ * Single query, four outcomes:
38
+ * 1. No signal backing → claim + resolve atomically. `resolved` is populated.
39
+ * 2. `metadata.signal_id` present, row unclaimed claim the row (preventing concurrent
40
+ * duplicate signals via FOR UPDATE serialization), then return signal info so the
41
+ * caller can signal the workflow. `signal_already_claimed = false`.
42
+ * 3. `metadata.signal_id` present, row already claimed and claim not expired →
43
+ * a concurrent caller is handling the signal. `signal_already_claimed = true`;
44
+ * caller returns 409 without re-signaling.
45
+ * 4. `signal_key` present (atomic conditionLT) → `claimed` and `resolved` both skip.
46
+ * Caller invokes SDK resolve to atomically mark resolved + deliver the signal.
47
+ *
48
+ * Signal_id hardening: the `claimed` CTE runs for signal_id rows (previously excluded).
49
+ * This stamps `assigned_to` on the row inside the FOR UPDATE transaction, so a
50
+ * concurrent second caller sees `signal_already_claimed = true` and aborts.
51
+ * The `resolved` CTE still skips signal_id rows — the workflow resolves durably.
42
52
  *
43
53
  * $1 = metadata filter (jsonb), $2 = userId, $3 = resolver_payload (jsonb),
44
54
  * $4 = metadata patch (jsonb, nullable), $5 = allowed roles (text[], null = no filter)
@@ -70,7 +80,7 @@ claimed AS (
70
80
  updated_at = NOW()
71
81
  FROM target
72
82
  WHERE e.id = target.id
73
- AND (target.metadata->>'signal_id') IS NULL
83
+ AND target.signal_key IS NULL
74
84
  RETURNING e.*
75
85
  ),
76
86
  resolved AS (
@@ -81,15 +91,20 @@ resolved AS (
81
91
  updated_at = NOW()
82
92
  FROM claimed
83
93
  WHERE e.id = claimed.id
94
+ AND (claimed.metadata->>'signal_id') IS NULL
84
95
  RETURNING e.*
85
96
  )
86
97
  SELECT
87
98
  resolved.*,
88
99
  target.id AS target_id,
89
100
  target.metadata->>'signal_id' AS signal_id,
101
+ target.signal_key AS signal_key,
90
102
  target.workflow_id AS target_workflow_id,
91
103
  target.workflow_type AS target_workflow_type,
92
104
  target.task_queue AS target_task_queue,
105
+ (target.assigned_to IS NOT NULL
106
+ AND target.assigned_until IS NOT NULL
107
+ AND target.assigned_until > NOW()) AS signal_already_claimed,
93
108
  CASE WHEN resolved.id IS NOT NULL THEN 'resolved' ELSE 'signal_required' END AS outcome
94
109
  FROM target
95
110
  LEFT JOIN resolved ON resolved.id = target.id`;
@@ -49,6 +49,7 @@ const logger_1 = require("../../lib/logger");
49
49
  const system_events_1 = require("../../lib/events/system-events");
50
50
  const namespaceService = __importStar(require("../namespace"));
51
51
  const deployer_helpers_1 = require("./deployer-helpers");
52
+ const pipe_validator_1 = require("./pipe-validator");
52
53
  // Re-export helpers so existing `import * from './deployer'` consumers keep working
53
54
  var deployer_helpers_2 = require("./deployer-helpers");
54
55
  Object.defineProperty(exports, "buildMergedYaml", { enumerable: true, get: function () { return deployer_helpers_2.buildMergedYaml; } });
@@ -90,6 +91,10 @@ async function deployAppId(appId, version) {
90
91
  await namespaceService.registerNamespace(appId);
91
92
  const mergedYaml = await (0, deployer_helpers_1.buildMergedYaml)(appId, version);
92
93
  logger_1.loggerRegistry.debug(`[yaml-workflow] merged YAML for ${appId} v${version}:\n${mergedYaml}`);
94
+ // Structural guard: reject YAML with malformed @pipe rows before they reach
95
+ // the HotMesh engine. A bad pipe passes deploy() silently but causes
96
+ // collation errors on every execution, poisoning the stream.
97
+ (0, pipe_validator_1.validatePipeStructure)(mergedYaml);
93
98
  const engine = await getEngine(appId);
94
99
  try {
95
100
  const manifest = await engine.deploy(mergedYaml);
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Structural validator for HotMesh YAML pipe syntax.
3
+ *
4
+ * HotMesh collation errors are silent at deploy time but catastrophic at
5
+ * runtime — a single malformed @pipe in an activated workflow poisons the
6
+ * engine's stream, causing every execution attempt to fail with a
7
+ * collation-error until the database is wiped.
8
+ *
9
+ * This module provides:
10
+ * validatePipeStructure — throws on any structurally invalid @pipe
11
+ * repairPipeStructure — auto-repairs safe-to-fix patterns, then validates
12
+ *
13
+ * Call repairPipeStructure before storing or deploying any YAML.
14
+ *
15
+ * ## What constitutes a valid @pipe row
16
+ *
17
+ * An @pipe value is a YAML sequence. Each element (row) must be one of:
18
+ * 1. A YAML sequence (array) — operands row OR function call row
19
+ * 2. A YAML mapping with '@pipe' key — nested sub-pipe
20
+ *
21
+ * Invalid row types that cause collation errors:
22
+ * - A bare string: '{@object.create}' → must be ['{@object.create}']
23
+ * - A number/bool: 42, true → must be wrapped in an array
24
+ * - A double-nested sequence: [[...]] → row itself is an array of arrays
25
+ */
26
+ export interface PipeViolation {
27
+ path: string;
28
+ rowIndex: number;
29
+ rowValue: unknown;
30
+ message: string;
31
+ repairable: boolean;
32
+ }
33
+ /**
34
+ * Parse the YAML and check every @pipe for structural violations.
35
+ * Throws a descriptive error listing all violations if any are found.
36
+ */
37
+ export declare function validatePipeStructure(yamlContent: string): void;
38
+ /**
39
+ * Auto-repair safe violations (bare strings/scalars → array rows), then
40
+ * validate that no unfixable violations remain.
41
+ *
42
+ * Returns the repaired YAML string. Throws if unfixable violations exist.
43
+ */
44
+ export declare function repairPipeStructure(yamlContent: string): string;
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ /**
3
+ * Structural validator for HotMesh YAML pipe syntax.
4
+ *
5
+ * HotMesh collation errors are silent at deploy time but catastrophic at
6
+ * runtime — a single malformed @pipe in an activated workflow poisons the
7
+ * engine's stream, causing every execution attempt to fail with a
8
+ * collation-error until the database is wiped.
9
+ *
10
+ * This module provides:
11
+ * validatePipeStructure — throws on any structurally invalid @pipe
12
+ * repairPipeStructure — auto-repairs safe-to-fix patterns, then validates
13
+ *
14
+ * Call repairPipeStructure before storing or deploying any YAML.
15
+ *
16
+ * ## What constitutes a valid @pipe row
17
+ *
18
+ * An @pipe value is a YAML sequence. Each element (row) must be one of:
19
+ * 1. A YAML sequence (array) — operands row OR function call row
20
+ * 2. A YAML mapping with '@pipe' key — nested sub-pipe
21
+ *
22
+ * Invalid row types that cause collation errors:
23
+ * - A bare string: '{@object.create}' → must be ['{@object.create}']
24
+ * - A number/bool: 42, true → must be wrapped in an array
25
+ * - A double-nested sequence: [[...]] → row itself is an array of arrays
26
+ */
27
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ var desc = Object.getOwnPropertyDescriptor(m, k);
30
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
31
+ desc = { enumerable: true, get: function() { return m[k]; } };
32
+ }
33
+ Object.defineProperty(o, k2, desc);
34
+ }) : (function(o, m, k, k2) {
35
+ if (k2 === undefined) k2 = k;
36
+ o[k2] = m[k];
37
+ }));
38
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
39
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
40
+ }) : function(o, v) {
41
+ o["default"] = v;
42
+ });
43
+ var __importStar = (this && this.__importStar) || (function () {
44
+ var ownKeys = function(o) {
45
+ ownKeys = Object.getOwnPropertyNames || function (o) {
46
+ var ar = [];
47
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
48
+ return ar;
49
+ };
50
+ return ownKeys(o);
51
+ };
52
+ return function (mod) {
53
+ if (mod && mod.__esModule) return mod;
54
+ var result = {};
55
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
56
+ __setModuleDefault(result, mod);
57
+ return result;
58
+ };
59
+ })();
60
+ Object.defineProperty(exports, "__esModule", { value: true });
61
+ exports.validatePipeStructure = validatePipeStructure;
62
+ exports.repairPipeStructure = repairPipeStructure;
63
+ const jsYaml = __importStar(require("js-yaml"));
64
+ // ── internal traversal ────────────────────────────────────────────────────────
65
+ function isPlainObject(v) {
66
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
67
+ }
68
+ function collectPipeViolations(node, path, violations) {
69
+ if (Array.isArray(node)) {
70
+ node.forEach((item, i) => collectPipeViolations(item, `${path}[${i}]`, violations));
71
+ return;
72
+ }
73
+ if (!isPlainObject(node))
74
+ return;
75
+ for (const [key, value] of Object.entries(node)) {
76
+ const childPath = `${path}.${key}`;
77
+ if (key === '@pipe') {
78
+ if (!Array.isArray(value)) {
79
+ violations.push({
80
+ path: childPath,
81
+ rowIndex: -1,
82
+ rowValue: value,
83
+ message: `@pipe value must be a sequence (array), got ${typeof value}`,
84
+ repairable: false,
85
+ });
86
+ continue;
87
+ }
88
+ value.forEach((row, i) => {
89
+ if (Array.isArray(row)) {
90
+ // Valid row — but check for double-nested arrays: [[...]]
91
+ if (row.length > 0 && row.every((el) => Array.isArray(el))) {
92
+ violations.push({
93
+ path: childPath,
94
+ rowIndex: i,
95
+ rowValue: row,
96
+ message: `row ${i} is a double-nested array (array of arrays) — each @pipe row must be a flat array`,
97
+ repairable: false,
98
+ });
99
+ }
100
+ }
101
+ else if (isPlainObject(row) && '@pipe' in row) {
102
+ // Valid nested sub-pipe — recurse into it
103
+ collectPipeViolations(row, `${childPath}[${i}]`, violations);
104
+ }
105
+ else if (typeof row === 'string') {
106
+ // '{@fn}' as bare string — repairable: wrap in array
107
+ violations.push({
108
+ path: childPath,
109
+ rowIndex: i,
110
+ rowValue: row,
111
+ message: `row ${i} is a bare string "${row}" — must be an array row like ['${row}']`,
112
+ repairable: true,
113
+ });
114
+ }
115
+ else if (typeof row === 'number' || typeof row === 'boolean') {
116
+ violations.push({
117
+ path: childPath,
118
+ rowIndex: i,
119
+ rowValue: row,
120
+ message: `row ${i} is a bare scalar ${JSON.stringify(row)} — must be wrapped in an array`,
121
+ repairable: true,
122
+ });
123
+ }
124
+ else {
125
+ violations.push({
126
+ path: childPath,
127
+ rowIndex: i,
128
+ rowValue: row,
129
+ message: `row ${i} has unexpected type ${typeof row}`,
130
+ repairable: false,
131
+ });
132
+ }
133
+ });
134
+ }
135
+ else {
136
+ collectPipeViolations(value, childPath, violations);
137
+ }
138
+ }
139
+ }
140
+ // ── public API ────────────────────────────────────────────────────────────────
141
+ /**
142
+ * Parse the YAML and check every @pipe for structural violations.
143
+ * Throws a descriptive error listing all violations if any are found.
144
+ */
145
+ function validatePipeStructure(yamlContent) {
146
+ let parsed;
147
+ try {
148
+ parsed = jsYaml.load(yamlContent);
149
+ }
150
+ catch (err) {
151
+ throw new Error(`YAML parse error: ${err.message}`);
152
+ }
153
+ const violations = [];
154
+ collectPipeViolations(parsed, 'root', violations);
155
+ if (violations.length > 0) {
156
+ const detail = violations
157
+ .map((v) => ` • ${v.path}[${v.rowIndex}]: ${v.message}`)
158
+ .join('\n');
159
+ throw new Error(`Malformed @pipe structure — ${violations.length} violation(s) detected. ` +
160
+ `This YAML will cause HotMesh collation errors at runtime and must not be deployed.\n${detail}`);
161
+ }
162
+ }
163
+ /**
164
+ * Auto-repair safe violations (bare strings/scalars → array rows), then
165
+ * validate that no unfixable violations remain.
166
+ *
167
+ * Returns the repaired YAML string. Throws if unfixable violations exist.
168
+ */
169
+ function repairPipeStructure(yamlContent) {
170
+ let parsed;
171
+ try {
172
+ parsed = jsYaml.load(yamlContent);
173
+ }
174
+ catch (err) {
175
+ throw new Error(`YAML parse error: ${err.message}`);
176
+ }
177
+ const repairCount = { n: 0 };
178
+ repairNode(parsed, repairCount);
179
+ if (repairCount.n > 0) {
180
+ // Re-serialize — use lineWidth: -1 to prevent wrapping
181
+ yamlContent = jsYaml.dump(parsed, { lineWidth: -1, noRefs: true, quotingType: "'" });
182
+ }
183
+ // Final validation pass — throws if unfixable violations remain
184
+ validatePipeStructure(yamlContent);
185
+ return yamlContent;
186
+ }
187
+ function repairNode(node, count) {
188
+ if (Array.isArray(node)) {
189
+ node.forEach((item) => repairNode(item, count));
190
+ return;
191
+ }
192
+ if (!isPlainObject(node))
193
+ return;
194
+ for (const [key, value] of Object.entries(node)) {
195
+ if (key === '@pipe' && Array.isArray(value)) {
196
+ const rows = value;
197
+ for (let i = 0; i < rows.length; i++) {
198
+ const row = rows[i];
199
+ if (typeof row === 'string' || typeof row === 'number' || typeof row === 'boolean') {
200
+ rows[i] = [row];
201
+ count.n++;
202
+ }
203
+ else if (isPlainObject(row) && '@pipe' in row) {
204
+ repairNode(row, count);
205
+ }
206
+ else if (Array.isArray(row)) {
207
+ repairNode(row, count);
208
+ }
209
+ }
210
+ }
211
+ else {
212
+ repairNode(value, count);
213
+ }
214
+ }
215
+ }
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.mcpWorkflowBuilder = mcpWorkflowBuilder;
37
37
  const hotmesh_1 = require("@hotmeshio/hotmesh");
38
+ const pipe_validator_1 = require("../../../services/yaml-workflow/pipe-validator");
38
39
  const activities = __importStar(require("./activities"));
39
40
  const prompts_1 = require("./prompts");
40
41
  const { loadBuilderTools, loadReferenceSection, callBuilderLLM, } = hotmesh_1.Durable.workflow.proxyActivities({
@@ -212,8 +213,20 @@ async function mcpWorkflowBuilder(envelope) {
212
213
  });
213
214
  continue;
214
215
  }
215
- // Fix known @pipe anti-patterns before returning
216
- const fixedYaml = fixPipePatterns(result.yaml);
216
+ // Fix known @pipe anti-patterns, then structurally validate —
217
+ // throws if unfixable violations remain, blocking deployment of poison YAML.
218
+ let fixedYaml = fixPipePatterns(result.yaml);
219
+ try {
220
+ fixedYaml = (0, pipe_validator_1.repairPipeStructure)(fixedYaml);
221
+ }
222
+ catch (pipeErr) {
223
+ messages.push({ role: 'assistant', content });
224
+ messages.push({
225
+ role: 'user',
226
+ content: `The generated YAML has structural @pipe errors that would cause runtime collation failures:\n${pipeErr.message}\n\nFix all @pipe rows: every row must be an array like ['{@fn}'] or [value1, value2]. Never use a bare string as a pipe row.`,
227
+ });
228
+ continue;
229
+ }
217
230
  return {
218
231
  type: 'return',
219
232
  data: {
@@ -1 +1 @@
1
- {"root":["../index.ts","../vitest.config.ts","../vitest.integration.config.ts","../adapters/express.ts","../api/agent-subscriptions.ts","../api/agents.ts","../api/auth-sso.ts","../api/auth.ts","../api/bot-accounts.ts","../api/capabilities.ts","../api/controlplane.ts","../api/dba.ts","../api/diagnostics.ts","../api/exports.ts","../api/files.ts","../api/index.ts","../api/insight.ts","../api/knowledge.ts","../api/maintenance.ts","../api/namespaces.ts","../api/overview.ts","../api/pipelines.ts","../api/roles.ts","../api/settings.ts","../api/tasks.ts","../api/topics.ts","../api/users.ts","../api/workflow-sets.ts","../api/escalations/bulk.ts","../api/escalations/cancel.ts","../api/escalations/claim.ts","../api/escalations/create.ts","../api/escalations/helpers.ts","../api/escalations/index.ts","../api/escalations/list.ts","../api/escalations/metadata.ts","../api/escalations/resolve.ts","../api/escalations/single.ts","../api/mcp/index.ts","../api/mcp/servers.ts","../api/mcp/tools.ts","../api/workflows/config.ts","../api/workflows/discovery.ts","../api/workflows/index.ts","../api/workflows/invocation.ts","../api/yaml-workflows/cron.ts","../api/yaml-workflows/crud.ts","../api/yaml-workflows/deploy.ts","../api/yaml-workflows/helpers.ts","../api/yaml-workflows/index.ts","../api/yaml-workflows/versions.ts","../bin/ltc.ts","../lib/cli/auth.ts","../lib/cli/client.ts","../lib/cli/compile.ts","../lib/cli/format.ts","../lib/cli/init.ts","../lib/cli/output.ts","../lib/cli/scanner.ts","../lib/cli/types.ts","../lib/cli/commands/escalations.ts","../lib/cli/commands/knowledge.ts","../lib/cli/commands/mcp.ts","../lib/cli/commands/pipelines.ts","../lib/cli/commands/status.ts","../lib/cli/commands/streams.ts","../lib/cli/commands/users.ts","../lib/cli/commands/workflows.ts","../lib/db/index.ts","../lib/db/migrate.ts","../lib/events/callback.ts","../lib/events/index.ts","../lib/events/matching.ts","../lib/events/memory.ts","../lib/events/nats-ws-proxy.ts","../lib/events/nats.ts","../lib/events/publish.ts","../lib/events/socketio.ts","../lib/events/system-events.ts","../lib/logger/index.ts","../lib/logger/pino.ts","../lib/storage/index.ts","../lib/storage/local.ts","../lib/storage/mime.ts","../lib/storage/s3.ts","../lib/storage/types.ts","../lib/telemetry/honeycomb.ts","../lib/telemetry/index.ts","../modules/auth.ts","../modules/config.ts","../modules/defaults.ts","../modules/ltconfig.ts","../modules/maintenance.ts","../modules/sso.ts","../modules/utils.ts","../routes/agents.ts","../routes/auth-sso.ts","../routes/auth.ts","../routes/bot-accounts.ts","../routes/capabilities.ts","../routes/controlplane.ts","../routes/dba.ts","../routes/delegation.ts","../routes/diagnostics.ts","../routes/docs.ts","../routes/exports.ts","../routes/file-browser.ts","../routes/files.ts","../routes/index.ts","../routes/insight.ts","../routes/knowledge.ts","../routes/maintenance.ts","../routes/mcp-endpoint.ts","../routes/mcp.ts","../routes/namespaces.ts","../routes/nats-credentials.ts","../routes/oauth.ts","../routes/overview.ts","../routes/pipelines.ts","../routes/roles.ts","../routes/settings.ts","../routes/tasks.ts","../routes/topics.ts","../routes/users.ts","../routes/workflow-sets.ts","../routes/escalations/bulk.ts","../routes/escalations/index.ts","../routes/escalations/list.ts","../routes/escalations/metadata.ts","../routes/escalations/resolve.ts","../routes/escalations/single.ts","../routes/workflows/config.ts","../routes/workflows/discovery.ts","../routes/workflows/index.ts","../routes/workflows/invocation.ts","../routes/yaml-workflows/cron.ts","../routes/yaml-workflows/crud.ts","../routes/yaml-workflows/deployment.ts","../routes/yaml-workflows/index.ts","../routes/yaml-workflows/versions.ts","../scripts/process-helpers.ts","../scripts/process.ts","../scripts/token.ts","../sdk/index.ts","../services/dba.ts","../services/hotmesh-utils.ts","../services/workflow-invocation.ts","../services/agent/index.ts","../services/agent/input-mapper.ts","../services/agent/sql.ts","../services/agent/subscription-sql.ts","../services/agent/subscriptions.ts","../services/agent/trigger-registry.ts","../services/auth/bot-api-key.ts","../services/auth/delegation.ts","../services/auth/index.ts","../services/auth/service-token.ts","../services/auth/sql.ts","../services/config/cache.ts","../services/config/index.ts","../services/config/provider.ts","../services/config/read.ts","../services/config/sql.ts","../services/config/write.ts","../services/controlplane/index.ts","../services/controlplane/quorum-bridge.ts","../services/controlplane/sql.ts","../services/controlplane/stream-messages-sql.ts","../services/controlplane/types.ts","../services/cron/index.ts","../services/diagnostics/index.ts","../services/diagnostics/patterns.ts","../services/escalation/bulk.ts","../services/escalation/client.ts","../services/escalation/crud.ts","../services/escalation/index.ts","../services/escalation/map.ts","../services/escalation/queries.ts","../services/escalation/sql.ts","../services/escalation/types.ts","../services/escalation-strategy/default.ts","../services/escalation-strategy/index.ts","../services/escalation-strategy/mcp.ts","../services/export/client.ts","../services/export/index.ts","../services/export/post-process.ts","../services/export/types.ts","../services/iam/activity.ts","../services/iam/bots.ts","../services/iam/context.ts","../services/iam/credentials.ts","../services/iam/envelope.ts","../services/iam/ephemeral.ts","../services/iam/index.ts","../services/iam/principal.ts","../services/iam/resolve.ts","../services/iam/sql.ts","../services/insight/index.ts","../services/insight/prompts.ts","../services/interceptor/activity-interceptor.ts","../services/interceptor/completion.ts","../services/interceptor/context.ts","../services/interceptor/escalation.ts","../services/interceptor/index.ts","../services/interceptor/lifecycle.ts","../services/interceptor/state.ts","../services/interceptor/types.ts","../services/interceptor/activities/config.ts","../services/interceptor/activities/escalation.ts","../services/interceptor/activities/index.ts","../services/interceptor/activities/task.ts","../services/interceptor/activities/workflow.ts","../services/llm/detect.ts","../services/llm/index.ts","../services/llm/translate.ts","../services/llm/types.ts","../services/llm/providers/anthropic.ts","../services/llm/providers/openai.ts","../services/maintenance/index.ts","../services/mcp/adapter.ts","../services/mcp/db.ts","../services/mcp/exposure.ts","../services/mcp/external-server.ts","../services/mcp/index.ts","../services/mcp/register-tool.ts","../services/mcp/seed-service-account.ts","../services/mcp/server-lifecycle.ts","../services/mcp/server-tools.ts","../services/mcp/server.ts","../services/mcp/sql.ts","../services/mcp/types.ts","../services/mcp/workflow-compiler-server.ts","../services/mcp/workflow-server.ts","../services/mcp/client/connection-dispatch.ts","../services/mcp/client/connection-lifecycle.ts","../services/mcp/client/connection-test.ts","../services/mcp/client/connection.ts","../services/mcp/client/index.ts","../services/mcp/client/tools.ts","../services/mcp/db-server/index.ts","../services/mcp/db-server/schemas.ts","../services/mcp/db-server/tools.ts","../services/mcp/playwright-server/index.ts","../services/mcp/playwright-server/lifecycle.ts","../services/mcp/playwright-server/schemas.ts","../services/mcp/playwright-server/tools.ts","../services/namespace/index.ts","../services/namespace/sql.ts","../services/namespace/types.ts","../services/oauth/crypto.ts","../services/oauth/db.ts","../services/oauth/index.ts","../services/oauth/sql.ts","../services/oauth/state.ts","../services/oauth/providers/anthropic.ts","../services/oauth/providers/github.ts","../services/oauth/providers/google.ts","../services/oauth/providers/index.ts","../services/oauth/providers/microsoft.ts","../services/oauth/providers/mock.ts","../services/oauth/providers/registry.ts","../services/oauth/providers/types.ts","../services/orchestrator/condition.ts","../services/orchestrator/index.ts","../services/orchestrator/types.ts","../services/overview/index.ts","../services/overview/sql.ts","../services/pipelines/enrichment.ts","../services/pipelines/events.ts","../services/pipelines/execution-builder.ts","../services/pipelines/index.ts","../services/pipelines/queries.ts","../services/pipelines/sql.ts","../services/pipelines/types.ts","../services/role/index.ts","../services/role/sql.ts","../services/role/types.ts","../services/task/crud.ts","../services/task/index.ts","../services/task/process.ts","../services/task/resolve.ts","../services/task/sql.ts","../services/task/types.ts","../services/topics/index.ts","../services/topics/sql.ts","../services/topics/system-topics.ts","../services/user/auth.ts","../services/user/crud.ts","../services/user/index.ts","../services/user/rbac.ts","../services/user/roles.ts","../services/user/seed-admin.ts","../services/user/sql.ts","../services/user/sso-provision.ts","../services/user/types.ts","../services/workers/registry.ts","../services/workflow-sets/db.ts","../services/workflow-sets/index.ts","../services/workflow-sets/sql.ts","../services/yaml-workflow/builder-regenerate.ts","../services/yaml-workflow/db-utils.ts","../services/yaml-workflow/db-versions.ts","../services/yaml-workflow/db.ts","../services/yaml-workflow/deployer-helpers.ts","../services/yaml-workflow/deployer.ts","../services/yaml-workflow/generator.ts","../services/yaml-workflow/index.ts","../services/yaml-workflow/input-analyzer-helpers.ts","../services/yaml-workflow/input-analyzer.ts","../services/yaml-workflow/invoke.ts","../services/yaml-workflow/sql.ts","../services/yaml-workflow/types.ts","../services/yaml-workflow/durable-compiler/index.ts","../services/yaml-workflow/durable-compiler/parser.ts","../services/yaml-workflow/durable-compiler/prompts.ts","../services/yaml-workflow/durable-compiler/types.ts","../services/yaml-workflow/pattern-detector/array-source.ts","../services/yaml-workflow/pattern-detector/collapse.ts","../services/yaml-workflow/pattern-detector/index.ts","../services/yaml-workflow/pattern-detector/run-detection.ts","../services/yaml-workflow/pattern-detector/types.ts","../services/yaml-workflow/pipeline/analyze.ts","../services/yaml-workflow/pipeline/extract-helpers.ts","../services/yaml-workflow/pipeline/extract.ts","../services/yaml-workflow/pipeline/index.ts","../services/yaml-workflow/pipeline/prompt-templates.ts","../services/yaml-workflow/pipeline/prompts.ts","../services/yaml-workflow/pipeline/validate.ts","../services/yaml-workflow/pipeline/build/dag-assembly.ts","../services/yaml-workflow/pipeline/build/dag.ts","../services/yaml-workflow/pipeline/build/index.ts","../services/yaml-workflow/pipeline/build/iteration.ts","../services/yaml-workflow/pipeline/build/metadata.ts","../services/yaml-workflow/pipeline/build/transform.ts","../services/yaml-workflow/pipeline/build/utils.ts","../services/yaml-workflow/pipeline/build/wiring.ts","../services/yaml-workflow/pipeline/compile/index.ts","../services/yaml-workflow/pipeline/compile/llm-call.ts","../services/yaml-workflow/pipeline/compile/parse-plan.ts","../services/yaml-workflow/pipeline/compile/summarize.ts","../services/yaml-workflow/workers/callbacks.ts","../services/yaml-workflow/workers/events.ts","../services/yaml-workflow/workers/index.ts","../services/yaml-workflow/workers/register.ts","../services/yaml-workflow/workers/scope.ts","../start/adapters.ts","../start/config.ts","../start/graph-workflows.ts","../start/index.ts","../start/server.ts","../start/socket-auth.ts","../start/workers.ts","../system/index.ts","../system/activities/claude-code.ts","../system/activities/file-storage.ts","../system/activities/http.ts","../system/activities/knowledge.ts","../system/activities/oauth.ts","../system/activities/schema-exchange.ts","../system/activities/sql.ts","../system/activities/triage/cache.ts","../system/activities/triage/context.ts","../system/activities/triage/discovery.ts","../system/activities/triage/index.ts","../system/activities/triage/llm.ts","../system/activities/triage/tools.ts","../system/mcp-servers/claude-code.ts","../system/mcp-servers/docs.ts","../system/mcp-servers/events.ts","../system/mcp-servers/file-storage.ts","../system/mcp-servers/http-fetch.ts","../system/mcp-servers/human-queue-schemas.ts","../system/mcp-servers/human-queue.ts","../system/mcp-servers/knowledge.ts","../system/mcp-servers/oauth.ts","../system/mcp-servers/schema-exchange.ts","../system/mcp-servers/translation.ts","../system/mcp-servers/vision-prompts.ts","../system/mcp-servers/vision.ts","../system/mcp-servers/workflow-compiler.ts","../system/mcp-servers/workflow.ts","../system/mcp-servers/admin/agent-subscriptions.ts","../system/mcp-servers/admin/agents.ts","../system/mcp-servers/admin/bot-accounts.ts","../system/mcp-servers/admin/controlplane.ts","../system/mcp-servers/admin/diagnostics.ts","../system/mcp-servers/admin/escalations.ts","../system/mcp-servers/admin/exports.ts","../system/mcp-servers/admin/index.ts","../system/mcp-servers/admin/maintenance.ts","../system/mcp-servers/admin/mcp-servers.ts","../system/mcp-servers/admin/overview.ts","../system/mcp-servers/admin/pipelines.ts","../system/mcp-servers/admin/schemas.ts","../system/mcp-servers/admin/settings.ts","../system/mcp-servers/admin/tasks.ts","../system/mcp-servers/admin/topics.ts","../system/mcp-servers/admin/users.ts","../system/mcp-servers/admin/workflow-config.ts","../system/mcp-servers/admin/workflows.ts","../system/mcp-servers/admin/yaml-workflows.ts","../system/mcp-servers/db-query/index.ts","../system/mcp-servers/db-query/schemas.ts","../system/mcp-servers/db-query/tools.ts","../system/seed/index.ts","../system/seed/tool-manifests-admin.ts","../system/seed/tool-manifests-data.ts","../system/seed/tool-manifests-escalation.ts","../system/seed/tool-manifests-events.ts","../system/seed/tool-manifests-knowledge.ts","../system/seed/tool-manifests-workflows.ts","../system/workflows/tool-result-guard.ts","../system/workflows/capability-invoke/activities.ts","../system/workflows/capability-invoke/index.ts","../system/workflows/mcp-deterministic/activities.ts","../system/workflows/mcp-deterministic/index.ts","../system/workflows/mcp-query/index.ts","../system/workflows/mcp-query/prompts.ts","../system/workflows/mcp-query/strategy-advisors.ts","../system/workflows/mcp-query/types.ts","../system/workflows/mcp-query/activities/caches.ts","../system/workflows/mcp-query/activities/discovery.ts","../system/workflows/mcp-query/activities/index.ts","../system/workflows/mcp-query/activities/llm.ts","../system/workflows/mcp-query/activities/tool-executor.ts","../system/workflows/mcp-query/activities/tool-loader.ts","../system/workflows/mcp-query-router/activities.ts","../system/workflows/mcp-query-router/index.ts","../system/workflows/mcp-query-router/prompts.ts","../system/workflows/mcp-triage/activities-proxy.ts","../system/workflows/mcp-triage/index.ts","../system/workflows/mcp-triage/prompts.ts","../system/workflows/mcp-triage/response-builders.ts","../system/workflows/mcp-triage/response.ts","../system/workflows/mcp-triage/types.ts","../system/workflows/mcp-triage-deterministic/activities.ts","../system/workflows/mcp-triage-deterministic/index.ts","../system/workflows/mcp-triage-router/activities.ts","../system/workflows/mcp-triage-router/index.ts","../system/workflows/mcp-workflow-builder/index.ts","../system/workflows/mcp-workflow-builder/prompts.ts","../system/workflows/mcp-workflow-builder/activities/caches.ts","../system/workflows/mcp-workflow-builder/activities/index.ts","../system/workflows/mcp-workflow-builder/activities/llm.ts","../system/workflows/mcp-workflow-builder/activities/tool-loader.ts","../system/workflows/mcp-workflow-planner/index.ts","../system/workflows/mcp-workflow-planner/prompts.ts","../system/workflows/mcp-workflow-planner/activities/analyze.ts","../system/workflows/mcp-workflow-planner/activities/index.ts","../system/workflows/mcp-workflow-planner/activities/persist.ts","../system/workflows/mcp-workflow-planner/activities/plan.ts","../system/workflows/shared/discovery.ts","../system/workflows/shared/index.ts","../system/workflows/shared/llm-caller.ts","../system/workflows/shared/prompts.ts","../system/workflows/shared/strategy-advisors.ts","../system/workflows/shared/tool-executor.ts","../system/workflows/shared/tool-loader.ts","../system/workflows/shared/types.ts","../types/agent.ts","../types/auth.ts","../types/config.ts","../types/delegation.ts","../types/discovery.ts","../types/envelope.ts","../types/escalation-strategy.ts","../types/escalation.ts","../types/events.ts","../types/export.ts","../types/express.d.ts","../types/index.ts","../types/logger.ts","../types/maintenance.ts","../types/mcp.ts","../types/oauth.ts","../types/sdk.ts","../types/startup.ts","../types/task.ts","../types/telemetry.ts","../types/tool-context.ts","../types/user.ts","../types/workflow-set.ts","../types/workflow.ts","../types/yaml-workflow.ts","../workers/index.ts"],"version":"5.9.3"}
1
+ {"root":["../index.ts","../vitest.config.ts","../vitest.integration.config.ts","../adapters/express.ts","../api/agent-subscriptions.ts","../api/agents.ts","../api/auth-sso.ts","../api/auth.ts","../api/bot-accounts.ts","../api/capabilities.ts","../api/controlplane.ts","../api/dba.ts","../api/diagnostics.ts","../api/exports.ts","../api/files.ts","../api/index.ts","../api/insight.ts","../api/knowledge.ts","../api/maintenance.ts","../api/namespaces.ts","../api/overview.ts","../api/pipelines.ts","../api/roles.ts","../api/settings.ts","../api/tasks.ts","../api/topics.ts","../api/users.ts","../api/workflow-sets.ts","../api/escalations/bulk.ts","../api/escalations/cancel.ts","../api/escalations/claim.ts","../api/escalations/create.ts","../api/escalations/helpers.ts","../api/escalations/index.ts","../api/escalations/list.ts","../api/escalations/metadata.ts","../api/escalations/resolve.ts","../api/escalations/single.ts","../api/mcp/index.ts","../api/mcp/servers.ts","../api/mcp/tools.ts","../api/workflows/config.ts","../api/workflows/discovery.ts","../api/workflows/index.ts","../api/workflows/invocation.ts","../api/yaml-workflows/cron.ts","../api/yaml-workflows/crud.ts","../api/yaml-workflows/deploy.ts","../api/yaml-workflows/helpers.ts","../api/yaml-workflows/index.ts","../api/yaml-workflows/versions.ts","../bin/ltc.ts","../lib/cli/auth.ts","../lib/cli/client.ts","../lib/cli/compile.ts","../lib/cli/format.ts","../lib/cli/init.ts","../lib/cli/output.ts","../lib/cli/scanner.ts","../lib/cli/types.ts","../lib/cli/commands/escalations.ts","../lib/cli/commands/knowledge.ts","../lib/cli/commands/mcp.ts","../lib/cli/commands/pipelines.ts","../lib/cli/commands/status.ts","../lib/cli/commands/streams.ts","../lib/cli/commands/users.ts","../lib/cli/commands/workflows.ts","../lib/db/index.ts","../lib/db/migrate.ts","../lib/events/callback.ts","../lib/events/index.ts","../lib/events/matching.ts","../lib/events/memory.ts","../lib/events/nats-ws-proxy.ts","../lib/events/nats.ts","../lib/events/publish.ts","../lib/events/socketio.ts","../lib/events/system-events.ts","../lib/logger/index.ts","../lib/logger/pino.ts","../lib/storage/index.ts","../lib/storage/local.ts","../lib/storage/mime.ts","../lib/storage/s3.ts","../lib/storage/types.ts","../lib/telemetry/honeycomb.ts","../lib/telemetry/index.ts","../modules/auth.ts","../modules/config.ts","../modules/defaults.ts","../modules/ltconfig.ts","../modules/maintenance.ts","../modules/sso.ts","../modules/utils.ts","../routes/agents.ts","../routes/auth-sso.ts","../routes/auth.ts","../routes/bot-accounts.ts","../routes/capabilities.ts","../routes/controlplane.ts","../routes/dba.ts","../routes/delegation.ts","../routes/diagnostics.ts","../routes/docs.ts","../routes/exports.ts","../routes/file-browser.ts","../routes/files.ts","../routes/index.ts","../routes/insight.ts","../routes/knowledge.ts","../routes/maintenance.ts","../routes/mcp-endpoint.ts","../routes/mcp.ts","../routes/namespaces.ts","../routes/nats-credentials.ts","../routes/oauth.ts","../routes/overview.ts","../routes/pipelines.ts","../routes/roles.ts","../routes/settings.ts","../routes/tasks.ts","../routes/topics.ts","../routes/users.ts","../routes/workflow-sets.ts","../routes/escalations/bulk.ts","../routes/escalations/index.ts","../routes/escalations/list.ts","../routes/escalations/metadata.ts","../routes/escalations/resolve.ts","../routes/escalations/single.ts","../routes/workflows/config.ts","../routes/workflows/discovery.ts","../routes/workflows/index.ts","../routes/workflows/invocation.ts","../routes/yaml-workflows/cron.ts","../routes/yaml-workflows/crud.ts","../routes/yaml-workflows/deployment.ts","../routes/yaml-workflows/index.ts","../routes/yaml-workflows/versions.ts","../scripts/process-helpers.ts","../scripts/process.ts","../scripts/token.ts","../sdk/index.ts","../services/dba.ts","../services/hotmesh-utils.ts","../services/workflow-invocation.ts","../services/agent/index.ts","../services/agent/input-mapper.ts","../services/agent/sql.ts","../services/agent/subscription-sql.ts","../services/agent/subscriptions.ts","../services/agent/trigger-registry.ts","../services/auth/bot-api-key.ts","../services/auth/delegation.ts","../services/auth/index.ts","../services/auth/service-token.ts","../services/auth/sql.ts","../services/config/cache.ts","../services/config/index.ts","../services/config/provider.ts","../services/config/read.ts","../services/config/sql.ts","../services/config/write.ts","../services/controlplane/index.ts","../services/controlplane/quorum-bridge.ts","../services/controlplane/sql.ts","../services/controlplane/stream-messages-sql.ts","../services/controlplane/types.ts","../services/cron/index.ts","../services/diagnostics/index.ts","../services/diagnostics/patterns.ts","../services/escalation/bulk.ts","../services/escalation/client.ts","../services/escalation/crud.ts","../services/escalation/index.ts","../services/escalation/map.ts","../services/escalation/queries.ts","../services/escalation/sql.ts","../services/escalation/types.ts","../services/escalation-strategy/default.ts","../services/escalation-strategy/index.ts","../services/escalation-strategy/mcp.ts","../services/export/client.ts","../services/export/index.ts","../services/export/post-process.ts","../services/export/types.ts","../services/iam/activity.ts","../services/iam/bots.ts","../services/iam/context.ts","../services/iam/credentials.ts","../services/iam/envelope.ts","../services/iam/ephemeral.ts","../services/iam/index.ts","../services/iam/principal.ts","../services/iam/resolve.ts","../services/iam/sql.ts","../services/insight/index.ts","../services/insight/prompts.ts","../services/interceptor/activity-interceptor.ts","../services/interceptor/completion.ts","../services/interceptor/context.ts","../services/interceptor/escalation.ts","../services/interceptor/index.ts","../services/interceptor/lifecycle.ts","../services/interceptor/state.ts","../services/interceptor/types.ts","../services/interceptor/activities/config.ts","../services/interceptor/activities/escalation.ts","../services/interceptor/activities/index.ts","../services/interceptor/activities/task.ts","../services/interceptor/activities/workflow.ts","../services/llm/detect.ts","../services/llm/index.ts","../services/llm/translate.ts","../services/llm/types.ts","../services/llm/providers/anthropic.ts","../services/llm/providers/openai.ts","../services/maintenance/index.ts","../services/mcp/adapter.ts","../services/mcp/db.ts","../services/mcp/exposure.ts","../services/mcp/external-server.ts","../services/mcp/index.ts","../services/mcp/register-tool.ts","../services/mcp/seed-service-account.ts","../services/mcp/server-lifecycle.ts","../services/mcp/server-tools.ts","../services/mcp/server.ts","../services/mcp/sql.ts","../services/mcp/types.ts","../services/mcp/workflow-compiler-server.ts","../services/mcp/workflow-server.ts","../services/mcp/client/connection-dispatch.ts","../services/mcp/client/connection-lifecycle.ts","../services/mcp/client/connection-test.ts","../services/mcp/client/connection.ts","../services/mcp/client/index.ts","../services/mcp/client/tools.ts","../services/mcp/db-server/index.ts","../services/mcp/db-server/schemas.ts","../services/mcp/db-server/tools.ts","../services/mcp/playwright-server/index.ts","../services/mcp/playwright-server/lifecycle.ts","../services/mcp/playwright-server/schemas.ts","../services/mcp/playwright-server/tools.ts","../services/namespace/index.ts","../services/namespace/sql.ts","../services/namespace/types.ts","../services/oauth/crypto.ts","../services/oauth/db.ts","../services/oauth/index.ts","../services/oauth/sql.ts","../services/oauth/state.ts","../services/oauth/providers/anthropic.ts","../services/oauth/providers/github.ts","../services/oauth/providers/google.ts","../services/oauth/providers/index.ts","../services/oauth/providers/microsoft.ts","../services/oauth/providers/mock.ts","../services/oauth/providers/registry.ts","../services/oauth/providers/types.ts","../services/orchestrator/condition.ts","../services/orchestrator/index.ts","../services/orchestrator/types.ts","../services/overview/index.ts","../services/overview/sql.ts","../services/pipelines/enrichment.ts","../services/pipelines/events.ts","../services/pipelines/execution-builder.ts","../services/pipelines/index.ts","../services/pipelines/queries.ts","../services/pipelines/sql.ts","../services/pipelines/types.ts","../services/role/index.ts","../services/role/sql.ts","../services/role/types.ts","../services/task/crud.ts","../services/task/index.ts","../services/task/process.ts","../services/task/resolve.ts","../services/task/sql.ts","../services/task/types.ts","../services/topics/index.ts","../services/topics/sql.ts","../services/topics/system-topics.ts","../services/user/auth.ts","../services/user/crud.ts","../services/user/index.ts","../services/user/rbac.ts","../services/user/roles.ts","../services/user/seed-admin.ts","../services/user/sql.ts","../services/user/sso-provision.ts","../services/user/types.ts","../services/workers/registry.ts","../services/workflow-sets/db.ts","../services/workflow-sets/index.ts","../services/workflow-sets/sql.ts","../services/yaml-workflow/builder-regenerate.ts","../services/yaml-workflow/db-utils.ts","../services/yaml-workflow/db-versions.ts","../services/yaml-workflow/db.ts","../services/yaml-workflow/deployer-helpers.ts","../services/yaml-workflow/deployer.ts","../services/yaml-workflow/generator.ts","../services/yaml-workflow/index.ts","../services/yaml-workflow/input-analyzer-helpers.ts","../services/yaml-workflow/input-analyzer.ts","../services/yaml-workflow/invoke.ts","../services/yaml-workflow/pipe-validator.ts","../services/yaml-workflow/sql.ts","../services/yaml-workflow/types.ts","../services/yaml-workflow/durable-compiler/index.ts","../services/yaml-workflow/durable-compiler/parser.ts","../services/yaml-workflow/durable-compiler/prompts.ts","../services/yaml-workflow/durable-compiler/types.ts","../services/yaml-workflow/pattern-detector/array-source.ts","../services/yaml-workflow/pattern-detector/collapse.ts","../services/yaml-workflow/pattern-detector/index.ts","../services/yaml-workflow/pattern-detector/run-detection.ts","../services/yaml-workflow/pattern-detector/types.ts","../services/yaml-workflow/pipeline/analyze.ts","../services/yaml-workflow/pipeline/extract-helpers.ts","../services/yaml-workflow/pipeline/extract.ts","../services/yaml-workflow/pipeline/index.ts","../services/yaml-workflow/pipeline/prompt-templates.ts","../services/yaml-workflow/pipeline/prompts.ts","../services/yaml-workflow/pipeline/validate.ts","../services/yaml-workflow/pipeline/build/dag-assembly.ts","../services/yaml-workflow/pipeline/build/dag.ts","../services/yaml-workflow/pipeline/build/index.ts","../services/yaml-workflow/pipeline/build/iteration.ts","../services/yaml-workflow/pipeline/build/metadata.ts","../services/yaml-workflow/pipeline/build/transform.ts","../services/yaml-workflow/pipeline/build/utils.ts","../services/yaml-workflow/pipeline/build/wiring.ts","../services/yaml-workflow/pipeline/compile/index.ts","../services/yaml-workflow/pipeline/compile/llm-call.ts","../services/yaml-workflow/pipeline/compile/parse-plan.ts","../services/yaml-workflow/pipeline/compile/summarize.ts","../services/yaml-workflow/workers/callbacks.ts","../services/yaml-workflow/workers/events.ts","../services/yaml-workflow/workers/index.ts","../services/yaml-workflow/workers/register.ts","../services/yaml-workflow/workers/scope.ts","../start/adapters.ts","../start/config.ts","../start/graph-workflows.ts","../start/index.ts","../start/server.ts","../start/socket-auth.ts","../start/workers.ts","../system/index.ts","../system/activities/claude-code.ts","../system/activities/file-storage.ts","../system/activities/http.ts","../system/activities/knowledge.ts","../system/activities/oauth.ts","../system/activities/schema-exchange.ts","../system/activities/sql.ts","../system/activities/triage/cache.ts","../system/activities/triage/context.ts","../system/activities/triage/discovery.ts","../system/activities/triage/index.ts","../system/activities/triage/llm.ts","../system/activities/triage/tools.ts","../system/mcp-servers/claude-code.ts","../system/mcp-servers/docs.ts","../system/mcp-servers/events.ts","../system/mcp-servers/file-storage.ts","../system/mcp-servers/http-fetch.ts","../system/mcp-servers/human-queue-schemas.ts","../system/mcp-servers/human-queue.ts","../system/mcp-servers/knowledge.ts","../system/mcp-servers/oauth.ts","../system/mcp-servers/schema-exchange.ts","../system/mcp-servers/translation.ts","../system/mcp-servers/vision-prompts.ts","../system/mcp-servers/vision.ts","../system/mcp-servers/workflow-compiler.ts","../system/mcp-servers/workflow.ts","../system/mcp-servers/admin/agent-subscriptions.ts","../system/mcp-servers/admin/agents.ts","../system/mcp-servers/admin/bot-accounts.ts","../system/mcp-servers/admin/controlplane.ts","../system/mcp-servers/admin/diagnostics.ts","../system/mcp-servers/admin/escalations.ts","../system/mcp-servers/admin/exports.ts","../system/mcp-servers/admin/index.ts","../system/mcp-servers/admin/maintenance.ts","../system/mcp-servers/admin/mcp-servers.ts","../system/mcp-servers/admin/overview.ts","../system/mcp-servers/admin/pipelines.ts","../system/mcp-servers/admin/schemas.ts","../system/mcp-servers/admin/settings.ts","../system/mcp-servers/admin/tasks.ts","../system/mcp-servers/admin/topics.ts","../system/mcp-servers/admin/users.ts","../system/mcp-servers/admin/workflow-config.ts","../system/mcp-servers/admin/workflows.ts","../system/mcp-servers/admin/yaml-workflows.ts","../system/mcp-servers/db-query/index.ts","../system/mcp-servers/db-query/schemas.ts","../system/mcp-servers/db-query/tools.ts","../system/seed/index.ts","../system/seed/tool-manifests-admin.ts","../system/seed/tool-manifests-data.ts","../system/seed/tool-manifests-escalation.ts","../system/seed/tool-manifests-events.ts","../system/seed/tool-manifests-knowledge.ts","../system/seed/tool-manifests-workflows.ts","../system/workflows/tool-result-guard.ts","../system/workflows/capability-invoke/activities.ts","../system/workflows/capability-invoke/index.ts","../system/workflows/mcp-deterministic/activities.ts","../system/workflows/mcp-deterministic/index.ts","../system/workflows/mcp-query/index.ts","../system/workflows/mcp-query/prompts.ts","../system/workflows/mcp-query/strategy-advisors.ts","../system/workflows/mcp-query/types.ts","../system/workflows/mcp-query/activities/caches.ts","../system/workflows/mcp-query/activities/discovery.ts","../system/workflows/mcp-query/activities/index.ts","../system/workflows/mcp-query/activities/llm.ts","../system/workflows/mcp-query/activities/tool-executor.ts","../system/workflows/mcp-query/activities/tool-loader.ts","../system/workflows/mcp-query-router/activities.ts","../system/workflows/mcp-query-router/index.ts","../system/workflows/mcp-query-router/prompts.ts","../system/workflows/mcp-triage/activities-proxy.ts","../system/workflows/mcp-triage/index.ts","../system/workflows/mcp-triage/prompts.ts","../system/workflows/mcp-triage/response-builders.ts","../system/workflows/mcp-triage/response.ts","../system/workflows/mcp-triage/types.ts","../system/workflows/mcp-triage-deterministic/activities.ts","../system/workflows/mcp-triage-deterministic/index.ts","../system/workflows/mcp-triage-router/activities.ts","../system/workflows/mcp-triage-router/index.ts","../system/workflows/mcp-workflow-builder/index.ts","../system/workflows/mcp-workflow-builder/prompts.ts","../system/workflows/mcp-workflow-builder/activities/caches.ts","../system/workflows/mcp-workflow-builder/activities/index.ts","../system/workflows/mcp-workflow-builder/activities/llm.ts","../system/workflows/mcp-workflow-builder/activities/tool-loader.ts","../system/workflows/mcp-workflow-planner/index.ts","../system/workflows/mcp-workflow-planner/prompts.ts","../system/workflows/mcp-workflow-planner/activities/analyze.ts","../system/workflows/mcp-workflow-planner/activities/index.ts","../system/workflows/mcp-workflow-planner/activities/persist.ts","../system/workflows/mcp-workflow-planner/activities/plan.ts","../system/workflows/shared/discovery.ts","../system/workflows/shared/index.ts","../system/workflows/shared/llm-caller.ts","../system/workflows/shared/prompts.ts","../system/workflows/shared/strategy-advisors.ts","../system/workflows/shared/tool-executor.ts","../system/workflows/shared/tool-loader.ts","../system/workflows/shared/types.ts","../types/agent.ts","../types/auth.ts","../types/config.ts","../types/delegation.ts","../types/discovery.ts","../types/envelope.ts","../types/escalation-strategy.ts","../types/escalation.ts","../types/events.ts","../types/export.ts","../types/express.d.ts","../types/index.ts","../types/logger.ts","../types/maintenance.ts","../types/mcp.ts","../types/oauth.ts","../types/sdk.ts","../types/startup.ts","../types/task.ts","../types/telemetry.ts","../types/tool-context.ts","../types/user.ts","../types/workflow-set.ts","../types/workflow.ts","../types/yaml-workflow.ts","../workers/index.ts"],"version":"5.9.3"}
@@ -594,6 +594,54 @@ metadata: {
594
594
 
595
595
  ---
596
596
 
597
+ ## Resolving from System Code
598
+
599
+ When a backend service (not the dashboard UI) needs to resolve an escalation — for example, an ingress handler that receives a webhook or processes a domain event — use the escalation SDK methods directly.
600
+
601
+ ### By escalation ID
602
+
603
+ Use when you already have the escalation UUID (e.g. stored in your own DB alongside the order):
604
+
605
+ ```typescript
606
+ const result = await lt.escalations.resolve({
607
+ id: escalationId,
608
+ resolverPayload: { approved: true, targetStatus: 'ready' },
609
+ });
610
+ ```
611
+
612
+ This routes through the full resolution path and works for all escalation types — atomic `conditionLT` (signal_key), legacy `conditionLT` (signal_id), and re-run-style escalations.
613
+
614
+ ### By metadata key-value pair
615
+
616
+ Use when you know a domain identifier (e.g. `orderId`) but not the escalation UUID. `resolveByMetadata` finds the highest-priority pending escalation matching the key-value pair and resolves it atomically — no pre-flight lookup, no TOCTOU:
617
+
618
+ ```typescript
619
+ const result = await lt.escalations.resolveByMetadata({
620
+ key: 'orderId',
621
+ value: orderId,
622
+ resolverPayload: { approved: true, targetStatus: 'ready' },
623
+ });
624
+
625
+ if (result.status === 404) {
626
+ // No pending escalation for this orderId
627
+ }
628
+ ```
629
+
630
+ This works for all escalation types including atomic `conditionLT` rows (those with `signal_key` set). The routing is transparent — the caller does not need to know which pattern the workflow used.
631
+
632
+ ### By signal key
633
+
634
+ When the signal key is deterministic and known to the caller (e.g. `station-done-${workflowId}`), use the direct signal-key path to skip the metadata lookup:
635
+
636
+ ```typescript
637
+ await lt.escalations.resolveBySignalKey({
638
+ signalKey: `station-done-${workflowId}`,
639
+ resolverPayload: { approved: true },
640
+ });
641
+ ```
642
+
643
+ ---
644
+
597
645
  ## Cancelling Escalations
598
646
 
599
647
  Escalations can be cancelled at any point before they are resolved. Cancellation is terminal — a cancelled escalation cannot be re-opened.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.5.7",
3
+ "version": "0.5.10",
4
4
  "description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -98,6 +98,7 @@
98
98
  "@playwright/test": "^1.58.2",
99
99
  "@types/bcryptjs": "^2.4.6",
100
100
  "@types/express": "^5.0.0",
101
+ "@types/js-yaml": "^4.0.9",
101
102
  "@types/jsonwebtoken": "^9.0.10",
102
103
  "@types/node": "^20.19.33",
103
104
  "@types/pg": "^8.10.0",