@o-lang/olang 1.4.0 → 1.4.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.
- package/package.json +1 -1
- package/src/parser/index.js +120 -30
- package/src/runtime/RuntimeAPI.js +46 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@o-lang/olang",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"author": "Olalekan Ogundipe <info@olang.cloud>",
|
|
5
5
|
"description": "O-Lang: A governance language for user-directed, rule-enforced agent workflows with native African language PII protection.",
|
|
6
6
|
"main": "./src/runtime/index.js",
|
package/src/parser/index.js
CHANGED
|
@@ -77,6 +77,11 @@ function parseWorkflowLines(lines, filename) {
|
|
|
77
77
|
let escalationLevels = [];
|
|
78
78
|
let currentLevel = null;
|
|
79
79
|
|
|
80
|
+
// ✅ ENHANCED: Track labels for blocks
|
|
81
|
+
let currentIfLabel = null;
|
|
82
|
+
let currentParallelLabel = null;
|
|
83
|
+
let currentEscalationLabel = null;
|
|
84
|
+
|
|
80
85
|
const flushCurrentStep = () => {
|
|
81
86
|
if (currentStep) {
|
|
82
87
|
workflow.steps.push(currentStep);
|
|
@@ -88,6 +93,15 @@ function parseWorkflowLines(lines, filename) {
|
|
|
88
93
|
let line = lines[i++].trim();
|
|
89
94
|
if (line === '' || line.startsWith('#')) continue;
|
|
90
95
|
|
|
96
|
+
// ✅ ENHANCED: Extract "Step N:" label if present
|
|
97
|
+
let stepLabel = null;
|
|
98
|
+
let strippedLine = line;
|
|
99
|
+
const labelMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
100
|
+
if (labelMatch) {
|
|
101
|
+
stepLabel = `Step ${labelMatch[1]}`;
|
|
102
|
+
strippedLine = labelMatch[2].trim();
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
// --- 1. Workflow Declaration ---
|
|
92
106
|
if (line.startsWith('Workflow ')) {
|
|
93
107
|
const match = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?$/i);
|
|
@@ -134,11 +148,13 @@ function parseWorkflowLines(lines, filename) {
|
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
// --- 4. Block: Escalation ---
|
|
137
|
-
|
|
151
|
+
// ✅ ENHANCED: Use strippedLine to match "Step N: Run in parallel with escalation:"
|
|
152
|
+
if (strippedLine.match(/^Run in parallel with escalation:$/i)) {
|
|
138
153
|
flushCurrentStep();
|
|
139
154
|
inEscalationBlock = true;
|
|
140
155
|
escalationLevels = [];
|
|
141
156
|
currentLevel = null;
|
|
157
|
+
currentEscalationLabel = stepLabel; // ✅ Save label
|
|
142
158
|
continue;
|
|
143
159
|
}
|
|
144
160
|
|
|
@@ -148,8 +164,10 @@ function parseWorkflowLines(lines, filename) {
|
|
|
148
164
|
workflow.steps.push({
|
|
149
165
|
type: 'escalation',
|
|
150
166
|
levels: escalationLevels,
|
|
151
|
-
stepNumber: workflow.steps.length + 1
|
|
167
|
+
stepNumber: workflow.steps.length + 1,
|
|
168
|
+
label: currentEscalationLabel // ✅ ENHANCED: Add label
|
|
152
169
|
});
|
|
170
|
+
currentEscalationLabel = null; // ✅ Reset
|
|
153
171
|
inEscalationBlock = false;
|
|
154
172
|
continue;
|
|
155
173
|
} else if (line.match(/^Level \d+:/i)) {
|
|
@@ -185,7 +203,8 @@ function parseWorkflowLines(lines, filename) {
|
|
|
185
203
|
}
|
|
186
204
|
|
|
187
205
|
// --- 5. Block: Parallel ---
|
|
188
|
-
|
|
206
|
+
// ✅ ENHANCED: Use strippedLine to match "Step N: Run in parallel for Xs"
|
|
207
|
+
const timedParMatch = strippedLine.match(/^Run in parallel for (\d+)\s*([smhd])$/i);
|
|
189
208
|
if (timedParMatch) {
|
|
190
209
|
flushCurrentStep();
|
|
191
210
|
const value = parseInt(timedParMatch[1]);
|
|
@@ -195,14 +214,17 @@ function parseWorkflowLines(lines, filename) {
|
|
|
195
214
|
|
|
196
215
|
inParallelBlock = true;
|
|
197
216
|
parallelSteps = [];
|
|
217
|
+
currentParallelLabel = stepLabel; // ✅ Save label
|
|
198
218
|
continue;
|
|
199
219
|
}
|
|
200
220
|
|
|
201
|
-
|
|
221
|
+
// ✅ ENHANCED: Use strippedLine to match "Step N: Run in parallel"
|
|
222
|
+
if (strippedLine.match(/^Run in parallel$/i)) {
|
|
202
223
|
flushCurrentStep();
|
|
203
224
|
inParallelBlock = true;
|
|
204
225
|
parallelSteps = [];
|
|
205
226
|
parallelTimeout = null;
|
|
227
|
+
currentParallelLabel = stepLabel; // ✅ Save label
|
|
206
228
|
continue;
|
|
207
229
|
}
|
|
208
230
|
|
|
@@ -214,8 +236,10 @@ function parseWorkflowLines(lines, filename) {
|
|
|
214
236
|
type: 'parallel',
|
|
215
237
|
steps: parsedParallel,
|
|
216
238
|
timeout: parallelTimeout,
|
|
217
|
-
stepNumber: workflow.steps.length + 1
|
|
239
|
+
stepNumber: workflow.steps.length + 1,
|
|
240
|
+
label: currentParallelLabel // ✅ ENHANCED: Add label
|
|
218
241
|
});
|
|
242
|
+
currentParallelLabel = null; // ✅ Reset
|
|
219
243
|
inParallelBlock = false;
|
|
220
244
|
parallelTimeout = null;
|
|
221
245
|
continue;
|
|
@@ -227,12 +251,13 @@ function parseWorkflowLines(lines, filename) {
|
|
|
227
251
|
|
|
228
252
|
// --- 6. Block: If / Else If / Else (FIXED STATE MACHINE) ---
|
|
229
253
|
|
|
230
|
-
//
|
|
231
|
-
if (
|
|
254
|
+
// ✅ ENHANCED: Use strippedLine to match "Step N: If ..."
|
|
255
|
+
if (strippedLine.match(/^(?:If|When)\s+(.+)$/i)) {
|
|
232
256
|
flushCurrentStep();
|
|
233
|
-
const ifMatch =
|
|
257
|
+
const ifMatch = strippedLine.match(/^(?:If|When)\s+(.+)$/i);
|
|
234
258
|
ifCondition = ifMatch[1].trim();
|
|
235
259
|
inIfBlock = true;
|
|
260
|
+
currentIfLabel = stepLabel; // ✅ Save label
|
|
236
261
|
|
|
237
262
|
// Reset accumulators
|
|
238
263
|
mainIfBody = [];
|
|
@@ -291,8 +316,10 @@ function parseWorkflowLines(lines, filename) {
|
|
|
291
316
|
body: parseBlock(mainIfBody), // Main body correctly isolated
|
|
292
317
|
elseIf: elseIfChain, // Else-if chain
|
|
293
318
|
elseBranch: inElseBlock ? parseBlock(currentBranchBody) : [], // Else body
|
|
294
|
-
stepNumber: workflow.steps.length + 1
|
|
319
|
+
stepNumber: workflow.steps.length + 1,
|
|
320
|
+
label: currentIfLabel // ✅ ENHANCED: Add label
|
|
295
321
|
});
|
|
322
|
+
currentIfLabel = null; // ✅ Reset
|
|
296
323
|
|
|
297
324
|
// Reset State
|
|
298
325
|
inIfBlock = false;
|
|
@@ -315,8 +342,25 @@ function parseWorkflowLines(lines, filename) {
|
|
|
315
342
|
|
|
316
343
|
// --- 7. Standard Steps & Keywords ---
|
|
317
344
|
|
|
345
|
+
// Calculate (NEW v1.4.0 — math expression evaluation)
|
|
346
|
+
// ✅ ENHANCED: Use strippedLine
|
|
347
|
+
const calcMatch = strippedLine.match(/^Calculate\s+(.+)$/i);
|
|
348
|
+
if (calcMatch) {
|
|
349
|
+
flushCurrentStep();
|
|
350
|
+
workflow.steps.push({
|
|
351
|
+
type: 'calculate',
|
|
352
|
+
expression: calcMatch[1].trim(),
|
|
353
|
+
stepNumber: workflow.steps.length + 1,
|
|
354
|
+
saveAs: null,
|
|
355
|
+
constraints: {},
|
|
356
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
357
|
+
});
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
318
361
|
// Connect: Connect "name" to url "..." OR Connect "name" to resolver "..."
|
|
319
|
-
|
|
362
|
+
// ✅ ENHANCED: Use strippedLine
|
|
363
|
+
const connectMatch = strippedLine.match(/^Connect\s+"([^"]+)"\s+to\s+(url|resolver)\s+"([^"]+)"$/i);
|
|
320
364
|
if (connectMatch) {
|
|
321
365
|
flushCurrentStep();
|
|
322
366
|
workflow.steps.push({
|
|
@@ -324,20 +368,23 @@ function parseWorkflowLines(lines, filename) {
|
|
|
324
368
|
resource: connectMatch[1],
|
|
325
369
|
endpoint: connectMatch[3],
|
|
326
370
|
targetType: connectMatch[2].toLowerCase(),
|
|
327
|
-
stepNumber: workflow.steps.length + 1
|
|
371
|
+
stepNumber: workflow.steps.length + 1,
|
|
372
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
328
373
|
});
|
|
329
374
|
continue;
|
|
330
375
|
}
|
|
331
376
|
|
|
332
377
|
// Use: Use "logicalName" as "resource"
|
|
333
|
-
|
|
378
|
+
// ✅ ENHANCED: Use strippedLine
|
|
379
|
+
const useMatch = strippedLine.match(/^Use\s+"([^"]+)"\s+as\s+"([^"]+)"$/i);
|
|
334
380
|
if (useMatch) {
|
|
335
381
|
flushCurrentStep();
|
|
336
382
|
workflow.steps.push({
|
|
337
383
|
type: 'agent_use',
|
|
338
384
|
logicalName: useMatch[1],
|
|
339
385
|
resource: useMatch[2],
|
|
340
|
-
stepNumber: workflow.steps.length + 1
|
|
386
|
+
stepNumber: workflow.steps.length + 1,
|
|
387
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
341
388
|
});
|
|
342
389
|
continue;
|
|
343
390
|
}
|
|
@@ -351,7 +398,8 @@ function parseWorkflowLines(lines, filename) {
|
|
|
351
398
|
stepNumber: parseInt(stepMatch[1], 10),
|
|
352
399
|
actionRaw: stepMatch[2].trim(),
|
|
353
400
|
saveAs: null,
|
|
354
|
-
constraints: {}
|
|
401
|
+
constraints: {},
|
|
402
|
+
label: `Step ${stepMatch[1]}` // ✅ ENHANCED: Add label
|
|
355
403
|
};
|
|
356
404
|
continue;
|
|
357
405
|
}
|
|
@@ -364,59 +412,68 @@ function parseWorkflowLines(lines, filename) {
|
|
|
364
412
|
}
|
|
365
413
|
|
|
366
414
|
// Debrief
|
|
367
|
-
|
|
415
|
+
// ✅ ENHANCED: Use strippedLine
|
|
416
|
+
const debriefMatch = strippedLine.match(/^Debrief\s+([^\s]+)\s+with\s+"([^"]*)"$/i);
|
|
368
417
|
if (debriefMatch) {
|
|
369
418
|
flushCurrentStep();
|
|
370
419
|
workflow.steps.push({
|
|
371
420
|
type: 'debrief',
|
|
372
421
|
agent: debriefMatch[1].trim(),
|
|
373
422
|
message: debriefMatch[2],
|
|
374
|
-
stepNumber: workflow.steps.length + 1
|
|
423
|
+
stepNumber: workflow.steps.length + 1,
|
|
424
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
375
425
|
});
|
|
376
426
|
continue;
|
|
377
427
|
}
|
|
378
428
|
|
|
379
429
|
// Prompt
|
|
380
|
-
|
|
430
|
+
// ✅ ENHANCED: Use strippedLine
|
|
431
|
+
const promptMatch = strippedLine.match(/^Prompt user to\s+"([^"]*)"$/i);
|
|
381
432
|
if (promptMatch) {
|
|
382
433
|
flushCurrentStep();
|
|
383
434
|
workflow.steps.push({
|
|
384
435
|
type: 'prompt',
|
|
385
436
|
question: promptMatch[1],
|
|
386
437
|
stepNumber: workflow.steps.length + 1,
|
|
387
|
-
saveAs: null
|
|
438
|
+
saveAs: null,
|
|
439
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
388
440
|
});
|
|
389
441
|
continue;
|
|
390
442
|
}
|
|
391
443
|
|
|
392
444
|
// Persist
|
|
393
|
-
|
|
445
|
+
// ✅ ENHANCED: Use strippedLine
|
|
446
|
+
const persistMatch = strippedLine.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]*)"$/i);
|
|
394
447
|
if (persistMatch) {
|
|
395
448
|
flushCurrentStep();
|
|
396
449
|
workflow.steps.push({
|
|
397
450
|
type: 'persist',
|
|
398
451
|
variable: persistMatch[1].trim(),
|
|
399
452
|
target: persistMatch[2],
|
|
400
|
-
stepNumber: workflow.steps.length + 1
|
|
453
|
+
stepNumber: workflow.steps.length + 1,
|
|
454
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
401
455
|
});
|
|
402
456
|
continue;
|
|
403
457
|
}
|
|
404
458
|
|
|
405
459
|
// Emit
|
|
406
|
-
|
|
460
|
+
// ✅ ENHANCED: Use strippedLine
|
|
461
|
+
const emitMatch = strippedLine.match(/^Emit\s+"([^"]+)"\s+with\s+(.+)$/i);
|
|
407
462
|
if (emitMatch) {
|
|
408
463
|
flushCurrentStep();
|
|
409
464
|
workflow.steps.push({
|
|
410
465
|
type: 'emit',
|
|
411
466
|
event: emitMatch[1],
|
|
412
467
|
payload: emitMatch[2].trim(),
|
|
413
|
-
stepNumber: workflow.steps.length + 1
|
|
468
|
+
stepNumber: workflow.steps.length + 1,
|
|
469
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
414
470
|
});
|
|
415
471
|
continue;
|
|
416
472
|
}
|
|
417
473
|
|
|
418
474
|
// Ask (Multiline support)
|
|
419
|
-
|
|
475
|
+
// ✅ ENHANCED: Use strippedLine
|
|
476
|
+
const askMatch = strippedLine.match(/^Ask\s+(.+)$/i);
|
|
420
477
|
if (askMatch) {
|
|
421
478
|
flushCurrentStep();
|
|
422
479
|
let actionContent = askMatch[1].trim();
|
|
@@ -434,7 +491,8 @@ function parseWorkflowLines(lines, filename) {
|
|
|
434
491
|
actionRaw: `Action ${actionContent}`,
|
|
435
492
|
stepNumber: workflow.steps.length + 1,
|
|
436
493
|
saveAs: null,
|
|
437
|
-
constraints: {}
|
|
494
|
+
constraints: {},
|
|
495
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
438
496
|
});
|
|
439
497
|
continue;
|
|
440
498
|
}
|
|
@@ -451,24 +509,26 @@ function parseWorkflowLines(lines, filename) {
|
|
|
451
509
|
}
|
|
452
510
|
|
|
453
511
|
// Fallback: Treat as action
|
|
454
|
-
|
|
512
|
+
// ✅ ENHANCED: Use strippedLine for actionRaw
|
|
513
|
+
if (strippedLine.trim() !== '') {
|
|
455
514
|
if (!currentStep) {
|
|
456
515
|
currentStep = {
|
|
457
516
|
type: 'action',
|
|
458
517
|
stepNumber: workflow.steps.length + 1,
|
|
459
|
-
actionRaw:
|
|
518
|
+
actionRaw: strippedLine,
|
|
460
519
|
saveAs: null,
|
|
461
|
-
constraints: {}
|
|
520
|
+
constraints: {},
|
|
521
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
462
522
|
};
|
|
463
523
|
} else {
|
|
464
|
-
currentStep.actionRaw += ' ' +
|
|
524
|
+
currentStep.actionRaw += ' ' + strippedLine;
|
|
465
525
|
}
|
|
466
526
|
}
|
|
467
527
|
}
|
|
468
528
|
|
|
469
529
|
flushCurrentStep();
|
|
470
530
|
|
|
471
|
-
// Post-process Save as in actionRaw
|
|
531
|
+
// Post-process Save as in actionRaw AND expression (calculate steps)
|
|
472
532
|
workflow.steps.forEach(step => {
|
|
473
533
|
if (step.actionRaw && step.saveAs === null) {
|
|
474
534
|
const saveInAction = step.actionRaw.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
@@ -477,6 +537,14 @@ function parseWorkflowLines(lines, filename) {
|
|
|
477
537
|
step.saveAs = normalizeSymbol(saveInAction[2].trim());
|
|
478
538
|
}
|
|
479
539
|
}
|
|
540
|
+
// ✅ NEW: Handle Calculate steps with inline Save as
|
|
541
|
+
if (step.type === 'calculate' && step.expression && step.saveAs === null) {
|
|
542
|
+
const saveInExpr = step.expression.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
543
|
+
if (saveInExpr) {
|
|
544
|
+
step.expression = saveInExpr[1].trim();
|
|
545
|
+
step.saveAs = normalizeSymbol(saveInExpr[2].trim());
|
|
546
|
+
}
|
|
547
|
+
}
|
|
480
548
|
if (step.saveAs) {
|
|
481
549
|
step.saveAs = normalizeSymbol(step.saveAs);
|
|
482
550
|
}
|
|
@@ -504,6 +572,19 @@ function parseBlock(lines) {
|
|
|
504
572
|
line = line.trim();
|
|
505
573
|
if (!line || line.startsWith('#')) continue;
|
|
506
574
|
|
|
575
|
+
// Calculate in Block (NEW v1.4.0)
|
|
576
|
+
const calcMatch = line.match(/^Calculate\s+(.+)$/i);
|
|
577
|
+
if (calcMatch) {
|
|
578
|
+
flush();
|
|
579
|
+
steps.push({
|
|
580
|
+
type: 'calculate',
|
|
581
|
+
expression: calcMatch[1].trim(),
|
|
582
|
+
saveAs: null,
|
|
583
|
+
constraints: {}
|
|
584
|
+
});
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
|
|
507
588
|
// Connect in Block
|
|
508
589
|
const connectMatch = line.match(/^Connect\s+"([^"]+)"\s+to\s+(url|resolver)\s+"([^"]+)"$/i);
|
|
509
590
|
if (connectMatch) {
|
|
@@ -538,7 +619,8 @@ function parseBlock(lines) {
|
|
|
538
619
|
stepNumber: parseInt(stepMatch[1], 10),
|
|
539
620
|
actionRaw: stepMatch[2].trim(),
|
|
540
621
|
saveAs: null,
|
|
541
|
-
constraints: {}
|
|
622
|
+
constraints: {},
|
|
623
|
+
label: `Step ${stepMatch[1]}` // ✅ ENHANCED: Add label
|
|
542
624
|
};
|
|
543
625
|
continue;
|
|
544
626
|
}
|
|
@@ -604,6 +686,14 @@ function parseBlock(lines) {
|
|
|
604
686
|
step.saveAs = normalizeSymbol(saveInAction[2].trim());
|
|
605
687
|
}
|
|
606
688
|
}
|
|
689
|
+
// ✅ NEW: Handle Calculate steps with inline Save as in blocks
|
|
690
|
+
if (step.type === 'calculate' && step.expression && step.saveAs === null) {
|
|
691
|
+
const saveInExpr = step.expression.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
692
|
+
if (saveInExpr) {
|
|
693
|
+
step.expression = saveInExpr[1].trim();
|
|
694
|
+
step.saveAs = normalizeSymbol(saveInExpr[2].trim());
|
|
695
|
+
}
|
|
696
|
+
}
|
|
607
697
|
if (step.saveAs) {
|
|
608
698
|
step.saveAs = normalizeSymbol(step.saveAs);
|
|
609
699
|
}
|
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const crypto = require('crypto'); // ✅ CRYPTOGRAPHIC AUDIT LOGS
|
|
4
4
|
|
|
5
5
|
// ✅ O-Lang Kernel Version (Safety Logic & Governance Rules)
|
|
6
|
-
const KERNEL_VERSION = '1.4.
|
|
6
|
+
const KERNEL_VERSION = '1.4.1'; // 🔁 Bumped: PII redaction engine added
|
|
7
7
|
|
|
8
8
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
9
|
// ✅ NEW v1.3.0 — SEPARATED PATTERN SETS
|
|
@@ -1624,8 +1624,15 @@ class RuntimeAPI {
|
|
|
1624
1624
|
}
|
|
1625
1625
|
}
|
|
1626
1626
|
|
|
1627
|
-
|
|
1627
|
+
// ✅ ENHANCED: Extract step location context for debugging
|
|
1628
|
+
const stepLabel = step.label || `unnamed_${step.type}_step`;
|
|
1629
|
+
const branchContext = step._parentLabel ? ` [Inside ${step._parentLabel} → ${step._branch} branch]` : '';
|
|
1630
|
+
|
|
1631
|
+
let errorMessage = `[O-Lang SAFETY] No resolver handled action: "${action}"\n`;
|
|
1632
|
+
errorMessage += ` → Location: ${stepLabel}${branchContext}\n`;
|
|
1633
|
+
errorMessage += ` → Workflow: ${this.context.workflow_name || 'unknown'}\n`;
|
|
1628
1634
|
errorMessage += `Attempted resolvers:\n`;
|
|
1635
|
+
|
|
1629
1636
|
resolverAttempts.forEach((attempt, i) => {
|
|
1630
1637
|
const namePad = attempt.name.padEnd(30);
|
|
1631
1638
|
if (attempt.status === 'skipped') {
|
|
@@ -1682,6 +1689,16 @@ class RuntimeAPI {
|
|
|
1682
1689
|
errorMessage += ` → Visit https://www.npmjs.com/search?q=%40o-lang for resolver packages\n`;
|
|
1683
1690
|
}
|
|
1684
1691
|
errorMessage += `\n🛑 Workflow halted to prevent unsafe data propagation to LLMs.`;
|
|
1692
|
+
|
|
1693
|
+
// ✅ ENHANCED: Log the failure with full context before throwing
|
|
1694
|
+
this._createAuditEntry('resolver_execution_failed', {
|
|
1695
|
+
step_label: stepLabel,
|
|
1696
|
+
branch_context: step._parentLabel ? `${step._parentLabel} (${step._branch})` : null,
|
|
1697
|
+
action: action,
|
|
1698
|
+
attempted_resolvers: resolverAttempts.map(a => a.name),
|
|
1699
|
+
severity: 'high'
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1685
1702
|
throw new Error(errorMessage);
|
|
1686
1703
|
};
|
|
1687
1704
|
|
|
@@ -1859,7 +1876,9 @@ class RuntimeAPI {
|
|
|
1859
1876
|
// 3. Auditability: Logs which condition was evaluated and which branch fired.
|
|
1860
1877
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1861
1878
|
|
|
1862
|
-
|
|
1879
|
+
case 'if': {
|
|
1880
|
+
const stepLabel = step.label || 'unnamed_if_block';
|
|
1881
|
+
|
|
1863
1882
|
// 1. Validate all symbols referenced in the main condition
|
|
1864
1883
|
const condSymbols = step.condition ? step.condition.match(/\{([^\}]+)\}/g) || [] : [];
|
|
1865
1884
|
let symbolsValid = true;
|
|
@@ -1873,6 +1892,7 @@ class RuntimeAPI {
|
|
|
1873
1892
|
|
|
1874
1893
|
if (!symbolsValid) {
|
|
1875
1894
|
this._createAuditEntry('condition_skipped', {
|
|
1895
|
+
step_label: stepLabel,
|
|
1876
1896
|
condition: step.condition,
|
|
1877
1897
|
reason: 'One or more symbols missing in context',
|
|
1878
1898
|
severity: 'warn'
|
|
@@ -1884,6 +1904,7 @@ class RuntimeAPI {
|
|
|
1884
1904
|
const mainPassed = this.evaluateCondition(step.condition, this.context);
|
|
1885
1905
|
|
|
1886
1906
|
this._createAuditEntry('condition_evaluated', {
|
|
1907
|
+
step_label: stepLabel,
|
|
1887
1908
|
condition: step.condition,
|
|
1888
1909
|
passed: mainPassed,
|
|
1889
1910
|
branch: 'if',
|
|
@@ -1892,7 +1913,12 @@ class RuntimeAPI {
|
|
|
1892
1913
|
|
|
1893
1914
|
if (mainPassed) {
|
|
1894
1915
|
if (step.body && Array.isArray(step.body)) {
|
|
1895
|
-
|
|
1916
|
+
// ✅ ENHANCED: Tag child steps with parent context for debugging
|
|
1917
|
+
for (const s of step.body) {
|
|
1918
|
+
s._parentLabel = stepLabel;
|
|
1919
|
+
s._branch = 'if';
|
|
1920
|
+
await this.executeStep(s, agentResolver);
|
|
1921
|
+
}
|
|
1896
1922
|
}
|
|
1897
1923
|
break; // Exit after successful if
|
|
1898
1924
|
}
|
|
@@ -1913,6 +1939,7 @@ class RuntimeAPI {
|
|
|
1913
1939
|
|
|
1914
1940
|
if (!branchSymbolsValid) {
|
|
1915
1941
|
this._createAuditEntry('condition_skipped', {
|
|
1942
|
+
step_label: stepLabel,
|
|
1916
1943
|
condition: branch.condition,
|
|
1917
1944
|
reason: 'One or more symbols missing in context',
|
|
1918
1945
|
severity: 'warn'
|
|
@@ -1923,6 +1950,7 @@ class RuntimeAPI {
|
|
|
1923
1950
|
const branchPassed = this.evaluateCondition(branch.condition, this.context);
|
|
1924
1951
|
|
|
1925
1952
|
this._createAuditEntry('condition_evaluated', {
|
|
1953
|
+
step_label: stepLabel,
|
|
1926
1954
|
condition: branch.condition,
|
|
1927
1955
|
passed: branchPassed,
|
|
1928
1956
|
branch: 'else-if',
|
|
@@ -1931,7 +1959,12 @@ class RuntimeAPI {
|
|
|
1931
1959
|
|
|
1932
1960
|
if (branchPassed) {
|
|
1933
1961
|
if (branch.body && Array.isArray(branch.body)) {
|
|
1934
|
-
|
|
1962
|
+
// ✅ ENHANCED: Tag child steps with parent context
|
|
1963
|
+
for (const s of branch.body) {
|
|
1964
|
+
s._parentLabel = stepLabel;
|
|
1965
|
+
s._branch = 'else-if';
|
|
1966
|
+
await this.executeStep(s, agentResolver);
|
|
1967
|
+
}
|
|
1935
1968
|
}
|
|
1936
1969
|
elseIfFired = true;
|
|
1937
1970
|
break;
|
|
@@ -1943,17 +1976,23 @@ class RuntimeAPI {
|
|
|
1943
1976
|
// 4. else fallback
|
|
1944
1977
|
if (step.elseBranch && Array.isArray(step.elseBranch)) {
|
|
1945
1978
|
this._createAuditEntry('condition_evaluated', {
|
|
1979
|
+
step_label: stepLabel,
|
|
1946
1980
|
condition: 'else',
|
|
1947
1981
|
passed: true,
|
|
1948
1982
|
branch: 'else',
|
|
1949
1983
|
severity: 'info'
|
|
1950
1984
|
});
|
|
1951
|
-
|
|
1985
|
+
// ✅ ENHANCED: Tag child steps with parent context
|
|
1986
|
+
for (const s of step.elseBranch) {
|
|
1987
|
+
s._parentLabel = stepLabel;
|
|
1988
|
+
s._branch = 'else';
|
|
1989
|
+
await this.executeStep(s, agentResolver);
|
|
1990
|
+
}
|
|
1952
1991
|
}
|
|
1953
1992
|
|
|
1954
1993
|
break;
|
|
1955
1994
|
}
|
|
1956
|
-
|
|
1995
|
+
|
|
1957
1996
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1958
1997
|
// PARALLEL
|
|
1959
1998
|
//
|