@o-lang/olang 1.2.14 → 1.2.16

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.
@@ -1,9 +1,13 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const crypto = require('crypto'); // ✅ CRYPTOGRAPHIC AUDIT LOGS
4
+
5
+ // ✅ O-Lang Kernel Version (Safety Logic & Governance Rules)
6
+ const KERNEL_VERSION = '1.2.15-alpha'; // 🔁 Update when safety rules change
3
7
 
4
8
  class RuntimeAPI {
5
9
  constructor({ verbose = false } = {}) {
6
- // console.log('✅ KERNEL FIX VERIFIED - Unwrapping active');
10
+ // console.log('✅ KERNEL FIX VERIFIED - Unwrapping active');
7
11
  this.context = {};
8
12
  this.resources = {};
9
13
  this.agentMap = {};
@@ -12,23 +16,31 @@ class RuntimeAPI {
12
16
  this.allowedResolvers = new Set();
13
17
  this.verbose = verbose;
14
18
  this.__warnings = [];
15
-
16
19
  const logsDir = path.resolve('./logs');
17
20
  if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
18
21
  this.disallowedLogFile = path.join(logsDir, 'disallowed_resolvers.json');
19
22
  this.disallowedAttempts = [];
20
-
23
+
21
24
  // ✅ NEW: Database client setup
22
25
  this.dbClient = null;
23
26
  this._initDbClient();
27
+
28
+ // ✅ NEW: Cryptographically verifiable audit logs
29
+ this.auditLog = [];
30
+ this.previousHash = 'GENESIS';
31
+ this.auditLogPrivateKey = process.env.OLANG_AUDIT_PRIVATE_KEY;
32
+ this.auditLogFile = path.join(logsDir, 'audit_log.json');
33
+ this.enableAuditLog = process.env.OLANG_AUDIT_LOG === 'true';
34
+
35
+ if (this.enableAuditLog && this.verbose) {
36
+ console.log('🔐 Cryptographically verifiable audit logging enabled');
37
+ }
24
38
  }
25
39
 
26
40
  // ✅ NEW: Initialize database client
27
41
  _initDbClient() {
28
42
  const dbType = process.env.OLANG_DB_TYPE; // 'postgres', 'mysql', 'mongodb', 'sqlite'
29
-
30
43
  if (!dbType) return; // DB persistence disabled
31
-
32
44
  try {
33
45
  switch (dbType.toLowerCase()) {
34
46
  case 'postgres':
@@ -45,7 +57,6 @@ class RuntimeAPI {
45
57
  })
46
58
  };
47
59
  break;
48
-
49
60
  case 'mysql':
50
61
  const mysql = require('mysql2/promise');
51
62
  this.dbClient = {
@@ -59,7 +70,6 @@ class RuntimeAPI {
59
70
  })
60
71
  };
61
72
  break;
62
-
63
73
  case 'mongodb':
64
74
  const { MongoClient } = require('mongodb');
65
75
  const uri = process.env.MONGO_URI || `mongodb://${process.env.DB_HOST || 'localhost'}:${process.env.DB_PORT || 27017}`;
@@ -68,7 +78,6 @@ class RuntimeAPI {
68
78
  client: new MongoClient(uri)
69
79
  };
70
80
  break;
71
-
72
81
  case 'sqlite':
73
82
  const Database = require('better-sqlite3');
74
83
  const dbPath = process.env.SQLITE_PATH || './olang.db';
@@ -79,11 +88,9 @@ class RuntimeAPI {
79
88
  client: new Database(dbPath)
80
89
  };
81
90
  break;
82
-
83
91
  default:
84
92
  throw new Error(`Unsupported database type: ${dbType}`);
85
93
  }
86
-
87
94
  if (this.verbose) {
88
95
  console.log(`🗄️ Database client initialized: ${dbType}`);
89
96
  }
@@ -93,6 +100,391 @@ class RuntimeAPI {
93
100
  }
94
101
  }
95
102
 
103
+ // ================================
104
+ // ✅ CRYPTOGRAPHIC AUDIT LOG METHODS
105
+ // ================================
106
+
107
+ /**
108
+ * Create a hash of data using SHA-256
109
+ */
110
+ _hash(data) {
111
+ return crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex');
112
+ }
113
+
114
+ /**
115
+ * Sign data with private key (if available)
116
+ */
117
+ _sign(data) {
118
+ if (!this.auditLogPrivateKey) return null;
119
+ try {
120
+ const sign = crypto.createSign('SHA256');
121
+ sign.update(data);
122
+ sign.end();
123
+ return sign.sign(this.auditLogPrivateKey, 'base64');
124
+ } catch (e) {
125
+ this.addWarning(`Audit log signing failed: ${e.message}`);
126
+ return null;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Create a cryptographically verifiable audit log entry
132
+ */
133
+ _createAuditEntry(event, details, context = {}) {
134
+ const timestamp = new Date().toISOString();
135
+ const entryData = {
136
+ timestamp,
137
+ event,
138
+ details,
139
+ workflow: this.context.workflow_name,
140
+ contextSnapshot: this._captureContextSnapshot(context),
141
+ previousHash: this.previousHash,
142
+ sequenceNumber: this.auditLog.length + 1
143
+ };
144
+
145
+ // Create hash of this entry
146
+ const entryHash = this._hash(entryData);
147
+
148
+ // Sign the entry if private key available
149
+ const signature = this._sign(entryHash);
150
+
151
+ const entry = {
152
+ ...entryData,
153
+ hash: entryHash,
154
+ signature,
155
+ publicKey: this.auditLogPrivateKey ?
156
+ crypto.createPublicKey(this.auditLogPrivateKey).export({
157
+ type: 'spki',
158
+ format: 'pem'
159
+ }) : null
160
+ };
161
+
162
+ // Update chain
163
+ this.previousHash = entryHash;
164
+ this.auditLog.push(entry);
165
+
166
+ // Persist to file if enabled
167
+ if (this.enableAuditLog) {
168
+ this._persistAuditLog();
169
+ }
170
+
171
+ return entry;
172
+ }
173
+
174
+ /**
175
+ * Capture relevant context snapshot for audit
176
+ */
177
+ _captureContextSnapshot(keys = []) {
178
+ const snapshot = {};
179
+ const keysToCapture = keys.length > 0 ? keys : [
180
+ 'workflow_name',
181
+ 'current_step',
182
+ 'agent_id'
183
+ ];
184
+
185
+ for (const key of keysToCapture) {
186
+ if (this.context[key] !== undefined) {
187
+ snapshot[key] = this.context[key];
188
+ }
189
+ }
190
+
191
+ return snapshot;
192
+ }
193
+
194
+ /**
195
+ * Persist audit log to file
196
+ */
197
+ _persistAuditLog() {
198
+ try {
199
+ fs.writeFileSync(
200
+ this.auditLogFile,
201
+ JSON.stringify(this.auditLog, null, 2),
202
+ 'utf8'
203
+ );
204
+
205
+ // Also persist to DB if configured
206
+ if (this.dbClient && process.env.OLANG_AUDIT_DB_PERSIST === 'true') {
207
+ this._persistAuditLogToDB();
208
+ }
209
+ } catch (e) {
210
+ this.addWarning(`Failed to persist audit log: ${e.message}`);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Persist audit log to database
216
+ */
217
+ async _persistAuditLogToDB() {
218
+ try {
219
+ const latestEntry = this.auditLog[this.auditLog.length - 1];
220
+ if (!latestEntry) return;
221
+
222
+ switch (this.dbClient.type) {
223
+ case 'postgres':
224
+ await this.dbClient.client.query(
225
+ `INSERT INTO audit_log (hash, previous_hash, event, details, timestamp, workflow_name, signature, sequence_number)
226
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
227
+ [
228
+ latestEntry.hash,
229
+ latestEntry.previousHash,
230
+ latestEntry.event,
231
+ JSON.stringify(latestEntry.details),
232
+ latestEntry.timestamp,
233
+ latestEntry.workflow,
234
+ latestEntry.signature,
235
+ latestEntry.sequenceNumber
236
+ ]
237
+ );
238
+ break;
239
+
240
+ case 'mysql':
241
+ await this.dbClient.client.execute(
242
+ `INSERT INTO audit_log (hash, previous_hash, event, details, timestamp, workflow_name, signature, sequence_number)
243
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
244
+ [
245
+ latestEntry.hash,
246
+ latestEntry.previousHash,
247
+ latestEntry.event,
248
+ JSON.stringify(latestEntry.details),
249
+ latestEntry.timestamp,
250
+ latestEntry.workflow,
251
+ latestEntry.signature,
252
+ latestEntry.sequenceNumber
253
+ ]
254
+ );
255
+ break;
256
+
257
+ case 'mongodb':
258
+ const db = this.dbClient.client.db(process.env.DB_NAME || 'olang');
259
+ await db.collection('audit_log').insertOne({
260
+ hash: latestEntry.hash,
261
+ previous_hash: latestEntry.previousHash,
262
+ event: latestEntry.event,
263
+ details: latestEntry.details,
264
+ timestamp: new Date(latestEntry.timestamp),
265
+ workflow_name: latestEntry.workflow,
266
+ signature: latestEntry.signature,
267
+ sequence_number: latestEntry.sequenceNumber
268
+ });
269
+ break;
270
+
271
+ case 'sqlite':
272
+ const stmt = this.dbClient.client.prepare(
273
+ `INSERT INTO audit_log (hash, previous_hash, event, details, timestamp, workflow_name, signature, sequence_number)
274
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
275
+ );
276
+ stmt.run(
277
+ latestEntry.hash,
278
+ latestEntry.previousHash,
279
+ latestEntry.event,
280
+ JSON.stringify(latestEntry.details),
281
+ latestEntry.timestamp,
282
+ latestEntry.workflow,
283
+ latestEntry.signature,
284
+ latestEntry.sequenceNumber
285
+ );
286
+ break;
287
+ }
288
+ } catch (e) {
289
+ this.addWarning(`Failed to persist audit log to DB: ${e.message}`);
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Verify the integrity of the audit log chain
295
+ */
296
+ verifyAuditLogIntegrity(auditLog = null) {
297
+ const log = auditLog || this.auditLog;
298
+ if (log.length === 0) {
299
+ return { valid: true, message: 'Audit log is empty' };
300
+ }
301
+
302
+ // Verify genesis block
303
+ if (log[0].previousHash !== 'GENESIS') {
304
+ return { valid: false, error: 'Invalid genesis block', failedAtIndex: 0 };
305
+ }
306
+
307
+ // Verify chain integrity
308
+ let previousHash = 'GENESIS';
309
+ for (let i = 0; i < log.length; i++) {
310
+ const entry = log[i];
311
+
312
+ // Check previous hash linkage
313
+ if (entry.previousHash !== previousHash) {
314
+ return {
315
+ valid: false,
316
+ error: `Hash chain broken at entry ${i}`,
317
+ failedAtIndex: i,
318
+ expected: previousHash,
319
+ actual: entry.previousHash
320
+ };
321
+ }
322
+
323
+ // Verify entry hash
324
+ const entryData = {
325
+ timestamp: entry.timestamp,
326
+ event: entry.event,
327
+ details: entry.details,
328
+ workflow: entry.workflow,
329
+ contextSnapshot: entry.contextSnapshot,
330
+ previousHash: entry.previousHash,
331
+ sequenceNumber: entry.sequenceNumber
332
+ };
333
+
334
+ const calculatedHash = this._hash(entryData);
335
+ if (calculatedHash !== entry.hash) {
336
+ return {
337
+ valid: false,
338
+ error: `Entry hash mismatch at index ${i}`,
339
+ failedAtIndex: i,
340
+ expected: calculatedHash,
341
+ actual: entry.hash
342
+ };
343
+ }
344
+
345
+ // Verify signature if present
346
+ if (entry.signature && entry.publicKey) {
347
+ try {
348
+ const verify = crypto.createVerify('SHA256');
349
+ verify.update(entry.hash);
350
+ verify.end();
351
+ const isValid = verify.verify(entry.publicKey, entry.signature, 'base64');
352
+ if (!isValid) {
353
+ return {
354
+ valid: false,
355
+ error: `Signature verification failed at entry ${i}`,
356
+ failedAtIndex: i
357
+ };
358
+ }
359
+ } catch (e) {
360
+ return {
361
+ valid: false,
362
+ error: `Signature verification error at entry ${i}: ${e.message}`,
363
+ failedAtIndex: i
364
+ };
365
+ }
366
+ }
367
+
368
+ previousHash = entry.hash;
369
+ }
370
+
371
+ return {
372
+ valid: true,
373
+ message: `Audit log verified successfully (${log.length} entries)`,
374
+ totalEntries: log.length,
375
+ lastHash: previousHash
376
+ };
377
+ }
378
+
379
+ /**
380
+ * Get audit log excerpt with proof
381
+ */
382
+ getAuditExcerpt(startIndex, endIndex) {
383
+ const excerpt = this.auditLog.slice(startIndex, endIndex + 1);
384
+ const proof = {
385
+ excerpt,
386
+ previousHash: startIndex > 0 ? this.auditLog[startIndex - 1].hash : 'GENESIS',
387
+ nextHash: endIndex < this.auditLog.length - 1 ? this.auditLog[endIndex + 1].hash : null,
388
+ totalEntries: this.auditLog.length
389
+ };
390
+
391
+ return proof;
392
+ }
393
+
394
+ /**
395
+ * Export audit log with verification data
396
+ */
397
+ exportAuditLog() {
398
+ const verification = this.verifyAuditLogIntegrity();
399
+ return {
400
+ auditLog: this.auditLog,
401
+ verification,
402
+ exportedAt: new Date().toISOString(),
403
+ totalEntries: this.auditLog.length,
404
+ merkleRoot: this._calculateMerkleRoot()
405
+ };
406
+ }
407
+
408
+ /**
409
+ * Calculate Merkle root of audit log
410
+ */
411
+ _calculateMerkleRoot() {
412
+ if (this.auditLog.length === 0) return null;
413
+
414
+ let hashes = this.auditLog.map(entry => entry.hash);
415
+
416
+ while (hashes.length > 1) {
417
+ const newLevel = [];
418
+ for (let i = 0; i < hashes.length; i += 2) {
419
+ const left = hashes[i];
420
+ const right = i + 1 < hashes.length ? hashes[i + 1] : left;
421
+ newLevel.push(this._hash(left + right));
422
+ }
423
+ hashes = newLevel;
424
+ }
425
+
426
+ return hashes[0];
427
+ }
428
+
429
+ /**
430
+ * Get audit summary
431
+ */
432
+ getAuditSummary() {
433
+ const verification = this.verifyAuditLogIntegrity();
434
+ return {
435
+ totalEntries: this.auditLog.length,
436
+ integrity: verification,
437
+ events: this.auditLog.map(e => e.event),
438
+ merkleRoot: this._calculateMerkleRoot()
439
+ };
440
+ }
441
+
442
+ // ================================
443
+ // ✅ GOVERNANCE METADATA HELPERS
444
+ // ================================
445
+
446
+ /**
447
+ * Generate immutable hash of governance profile
448
+ * Includes: allowed resolvers, constraints, policy flags
449
+ */
450
+ _generateGovernanceProfileHash(workflow) {
451
+ const profile = {
452
+ version: '1.0',
453
+ allowedResolvers: Array.from(this.allowedResolvers).sort(),
454
+ maxGenerations: workflow.maxGenerations,
455
+ strictInputs: process.env.OLANG_STRICT_INPUTS === 'true',
456
+ semanticValidation: true,
457
+ hallucinationPrevention: true,
458
+ resolverPolicy: 'allowlist-only',
459
+ timestamp: new Date().toISOString()
460
+ };
461
+
462
+ return crypto.createHash('sha256')
463
+ .update(JSON.stringify(profile))
464
+ .digest('hex');
465
+ }
466
+
467
+ /**
468
+ * Get runtime metadata for external verification
469
+ */
470
+ getRuntimeMetadata() {
471
+ return {
472
+ runtime: 'O-Lang Kernel',
473
+ version: KERNEL_VERSION,
474
+ features: {
475
+ semanticValidation: true,
476
+ hallucinationPrevention: true,
477
+ cryptographicAudit: true,
478
+ multiDatabaseSupport: true
479
+ },
480
+ environment: {
481
+ auditEnabled: this.enableAuditLog,
482
+ dbType: this.dbClient?.type || 'none',
483
+ strictMode: process.env.OLANG_STRICT_INPUTS === 'true'
484
+ }
485
+ };
486
+ }
487
+
96
488
  // -----------------------------
97
489
  // ✅ SEMANTIC ENFORCEMENT HELPER
98
490
  // -----------------------------
@@ -106,15 +498,12 @@ class RuntimeAPI {
106
498
  used_by: stepType,
107
499
  phase: 'execution'
108
500
  };
109
-
110
501
  // Emit semantic event (for observability)
111
502
  this.emit('semantic_violation', error);
112
-
113
503
  // Log as error (not warning)
114
504
  if (this.verbose) {
115
505
  console.error(`[O-Lang SEMANTIC] Missing required symbol "${symbol}" for ${stepType}`);
116
506
  }
117
-
118
507
  return false;
119
508
  }
120
509
  return true;
@@ -154,7 +543,20 @@ class RuntimeAPI {
154
543
  const entry = { resolver: resolverName, step: stepAction, timestamp: new Date().toISOString() };
155
544
  fs.appendFileSync(this.disallowedLogFile, JSON.stringify(entry) + '\n', 'utf8');
156
545
  this.disallowedAttempts.push(entry);
157
-
546
+
547
+ // ✅ AUDIT LOG: Security violation with governance context
548
+ this._createAuditEntry('security_violation', {
549
+ type: 'disallowed_resolver',
550
+ resolver: resolverName,
551
+ step: stepAction,
552
+ severity: 'high',
553
+ kernel_version: KERNEL_VERSION,
554
+ governance_profile_hash: this._generateGovernanceProfileHash({
555
+ allowedResolvers: Array.from(this.allowedResolvers),
556
+ maxGenerations: null
557
+ })
558
+ });
559
+
158
560
  if (this.verbose) {
159
561
  console.warn(`[O-Lang] Disallowed resolver blocked: ${resolverName} | step: ${stepAction}`);
160
562
  }
@@ -193,7 +595,6 @@ class RuntimeAPI {
193
595
  const manifest = resolver.manifest;
194
596
  const endpoint = manifest.endpoint;
195
597
  const timeoutMs = manifest.timeout_ms || 30000;
196
-
197
598
  const payload = {
198
599
  action,
199
600
  context,
@@ -204,7 +605,6 @@ class RuntimeAPI {
204
605
 
205
606
  const controller = new AbortController();
206
607
  const timer = setTimeout(() => controller.abort(), timeoutMs);
207
-
208
608
  try {
209
609
  const res = await fetch(`${endpoint}/resolve`, {
210
610
  method: 'POST',
@@ -212,17 +612,13 @@ class RuntimeAPI {
212
612
  body: JSON.stringify(payload),
213
613
  signal: controller.signal
214
614
  });
215
-
216
615
  if (!res.ok) {
217
616
  throw new Error(`HTTP ${res.status} ${res.statusText}`);
218
617
  }
219
-
220
618
  const json = await res.json();
221
-
222
619
  if (json?.error) {
223
620
  throw new Error(json.error.message || 'External resolver error');
224
621
  }
225
-
226
622
  return json.result;
227
623
  } catch (err) {
228
624
  if (err.name === 'AbortError') {
@@ -279,11 +675,9 @@ class RuntimeAPI {
279
675
  if (typeof value === 'string') return `"${value.replace(/"/g, '\\"')}"`;
280
676
  return value !== undefined ? value : 0;
281
677
  });
282
-
283
678
  const funcNames = Object.keys(this.mathFunctions);
284
679
  const safeFunc = {};
285
680
  funcNames.forEach(fn => safeFunc[fn] = this.mathFunctions[fn]);
286
-
287
681
  try {
288
682
  const f = new Function(...funcNames, `return ${expr};`);
289
683
  return f(...funcNames.map(fn => safeFunc[fn]));
@@ -307,18 +701,15 @@ class RuntimeAPI {
307
701
  _safeInterpolate(template, context, contextType = 'action') {
308
702
  return template.replace(/\{([^\}]+)\}/g, (_, path) => {
309
703
  const value = this.getNested(context, path.trim());
310
-
311
704
  if (value === undefined) {
312
705
  return `{${path}}`; // Preserve placeholder for downstream validation
313
706
  }
314
-
315
707
  // 🔒 SAFETY GUARD: Block object/array interpolation into string contexts
316
708
  if (value !== null && typeof value === 'object') {
317
709
  const type = Array.isArray(value) ? 'array' : 'object';
318
- const keys = Object.keys(value).length > 0
319
- ? Object.keys(value).join(', ')
710
+ const keys = Object.keys(value).length > 0
711
+ ? Object.keys(value).join(', ')
320
712
  : '(empty)';
321
-
322
713
  throw new Error(
323
714
  `[O-Lang SAFETY] Cannot interpolate ${type} "{${path}}" into ${contextType}.\n` +
324
715
  ` → Contains fields: ${keys}\n` +
@@ -326,217 +717,162 @@ class RuntimeAPI {
326
717
  `\n🛑 Halting to prevent data corruption → LLM hallucination.`
327
718
  );
328
719
  }
329
-
330
720
  return String(value);
331
721
  });
332
722
  }
333
723
 
334
724
  // -----------------------------
335
- // ✅ KERNEL-LEVEL LLM HALLUCINATION PREVENTION (CONJUGATION-AWARE + EVASION-RESISTANT)
336
- // -----------------------------
337
- _validateLLMOutput(output, actionContext) {
338
- if (!output || typeof output !== 'string') return { passed: true };
339
-
340
- // 🔑 Extract allowed capabilities from workflow allowlist
341
- const allowedCapabilities = Array.from(this.allowedResolvers)
342
- .filter(name => !name.startsWith('llm-') && name !== 'builtInMathResolver')
343
- .map(name => name.replace('@o-lang/', '').replace(/-resolver$/, ''));
344
-
345
- // 🔒 CONJUGATION-AWARE + EVASION-RESISTANT PAN-AFRICAN INTENT DETECTION
346
- const forbiddenPatterns = [
347
- // ────────────────────────────────────────────────
348
- // 🇳🇬 NIGERIAN LANGUAGES (Conjugation-aware)
349
- // ────────────────────────────────────────────────
350
-
351
- // Yoruba (yo) - Perfective "ti" + Progressive "ń/ǹ/n"
352
- { pattern: /\bti\s+(?:fi|san|gba|da|lo)\b/i, capability: 'unauthorized_action', lang: 'yo' }, // "has transferred/paid/withdrawn"
353
- { pattern: /\b(?:ń|ǹ|n)\s+(?:fi|san|gba)\b/i, capability: 'unauthorized_action', lang: 'yo' }, // Progressive "is transferring/paying"
354
- { pattern: /\b(fi\s+(?:owo|ẹ̀wọ̀|ewo|ku|fun|s'ọkọọ))\b/i, capability: 'transfer', lang: 'yo' },
355
- { pattern: /\b(san\s+(?:owo|ẹ̀wọ̀|ewo|fun|wo))\b/i, capability: 'payment', lang: 'yo' },
356
- { pattern: /\b(gba\s+owo)\b/i, capability: 'withdrawal', lang: 'yo' },
357
- { pattern: /\b(mo\s+ti\s+(?:fi|san|gba))\b/i, capability: 'unauthorized_action', lang: 'yo' },
358
-
359
- // Hausa (ha) - Perfective "ya/ta/su" + Future "za a/za ta"
360
- { pattern: /\b(?:ya|ta|su)\s+(?:ciyar|biya|sahawa|sake)\b/i, capability: 'unauthorized_action', lang: 'ha' }, // "he/she/they transferred/paid/withdrew/deposited"
361
- { pattern: /\b(?:za\sa|za\s+ta)\s+(?:ciyar|biya)\b/i, capability: 'unauthorized_action', lang: 'ha' }, // Future "will transfer/pay"
362
- { pattern: /\b(ciyar\s*(?:da)?|ciya\s*(?:da)?|shiga\s+kuɗi)\b/i, capability: 'transfer', lang: 'ha' },
363
- { pattern: /\b(biya\s*(?:da)?)\b/i, capability: 'payment', lang: 'ha' },
364
- { pattern: /\b(sahaw[ae]\s+kuɗi)\b/i, capability: 'withdrawal', lang: 'ha' },
365
- { pattern: /\b(ina\s+(?:ciyar|biya|sahawa))\b/i, capability: 'unauthorized_action', lang: 'ha' },
366
-
367
- // Igbo (ig) - Perfective suffixes
368
- { pattern: /\b(?:ziri|bururu|tinyere|gbara)\b/i, capability: 'unauthorized_action', lang: 'ig' }, // "has sent/carried/deposited/withdrawn"
369
- { pattern: /\b(zipu\s+(?:ego|moni|isi|na))\b/i, capability: 'transfer', lang: 'ig' },
370
- { pattern: /\b(buru\s+(?:ego|moni|isi))\b/i, capability: 'transfer', lang: 'ig' },
371
- { pattern: /\b(tinye\s+(?:ego|moni|isi))\b/i, capability: 'deposit', lang: 'ig' },
372
- { pattern: /\b(m\s+(?:ziri|buru|zipuru|tinyere))\b/i, capability: 'unauthorized_action', lang: 'ig' },
373
-
374
- // ────────────────────────────────────────────────
375
- // 🌍 PAN-AFRICAN LANGUAGES (Conjugation-aware + Evasion-resistant)
376
- // ────────────────────────────────────────────────
377
-
378
- // Swahili (sw) - ALL ASPECTS: Perfect, Continuous Passive, Future
379
- { pattern: /\b(?:ni|u|a|tu|m|wa|ki|vi|zi|i)\s*me\s*(?:ongeza|weka|tuma|peleka|lipa|wasilisha)\b/i, capability: 'unauthorized_action', lang: 'sw' }, // Perfect: "nimeongeza" (I have added)
380
- { pattern: /\b(?:kime|lime|ime|ume|nime|vime|zyme|yame|mame)(?:ongezwa|wekwa|tumwa|pelekwa|lipwa|wasilishwa|fanyika)\b/i, capability: 'unauthorized_action', lang: 'sw' }, // Passive perfect: "kimeongezwa" (has been added)
381
- { pattern: /\b(?:ki|vi|mi|ma|u|wa|i|zi|ya|li|tu|mu|a|pa|ku)na(?:cho|vyo|yo|lo|mo|o)?(?:tum|pelek|wasil|ongez|wek|lip)\w*wa\b/i, capability: 'unauthorized_action', lang: 'sw' }, // Continuous passive: "kinachowasilishwa" (is being delivered) ← CRITICAL FIX
382
- { pattern: /\b(?:ki|vi|mi|ma|u|wa|i|zi|ya|li|tu|mu|a|pa|ku)ta(?:tum|pelek|wasil|ongez|wek|lip)\w*\b/i, capability: 'unauthorized_action', lang: 'sw' }, // Future: "kitatuma" (will send)
383
- { pattern: /\b(tuma\s+(?:pesa|fedha)|pelek[ae]?\s+(?:pesa|fedha)|wasilisha)\b/i, capability: 'transfer', lang: 'sw' },
384
- { pattern: /\b(lipa|maliza\s+malipo)\b/i, capability: 'payment', lang: 'sw' },
385
- { pattern: /\b(ongez[ae]?\s*(?:kiasi|pesa|fedha)|wek[ae]?\s+(?:katika|ndani)\s+(?:akaunti|hisa))\b/i, capability: 'deposit', lang: 'sw' },
386
- { pattern: /\b(nime(?:tuma|lipa|ongeza|weka|peleka))\b/i, capability: 'unauthorized_action', lang: 'sw' },
387
-
388
- // Amharic (am) - Perfective suffix (Ethiopic script)
389
- { pattern: /[\u1200-\u137F]{0,4}(?:ተላላፈ|ላክ|ክፈል|ጨምር|ወጣ|ገባ)[\u1200-\u137F]{0,2}(?:\u1205|\u122d|\u1265)[\u1200-\u137F]{0,2}/u, capability: 'financial_action', lang: 'am' },
390
-
391
- // Oromo (om) - Perfective "ni...e"
392
- { pattern: /\bni\s+(?:kuufe|dhiibe|kennine|gurgure)\b/i, capability: 'unauthorized_action', lang: 'om' },
393
- { pattern: /\b(kuuf\s+(?:qilleensaa|bilbila)|dhiib\s+(?:qilleensaa|bilbila))\b/i, capability: 'transfer', lang: 'om' },
394
- { pattern: /\b(kenn\s*i|gurgur\s*i)\b/i, capability: 'payment', lang: 'om' },
395
-
396
- // Fula (ff)
397
- { pattern: /\b(sakkit\s+(?:ndo|ndoo)|tawt\s+(?:ndo|ndoo))\b/i, capability: 'transfer', lang: 'ff' },
398
- { pattern: /\b(jokk\s*i|soodug\s*i)\b/i, capability: 'payment', lang: 'ff' },
399
-
400
- // Somali (so) - Perfective "waxaa"
401
- { pattern: /\bwaxaa\s+(?:diray|bixiyay|ku\s+daray|sameeyay)\b/i, capability: 'unauthorized_action', lang: 'so' },
402
- { pattern: /\b(dir\s+(?:lacag|maal|qarsoon))\b/i, capability: 'transfer', lang: 'so' },
403
- { pattern: /\b(bixi|bixis\s*o)\b/i, capability: 'payment', lang: 'so' },
404
-
405
- // Zulu (zu) - Perfective "-ile"
406
- { pattern: /\b(?:thumel|hlawul|fik)\s*ile\b/i, capability: 'unauthorized_action', lang: 'zu' },
407
- { pattern: /\b(thumel\s*a\s+(?:imali|imali))\b/i, capability: 'transfer', lang: 'zu' },
408
- { pattern: /\b(hlawul\s*a|hlawulel\s*a)\b/i, capability: 'payment', lang: 'zu' },
409
- { pattern: /\b(siyithumel\s*e|siyihlawul\s*e)\b/i, capability: 'unauthorized_action', lang: 'zu' },
410
-
411
- // Shona (sn) - Perfective "-a/-e"
412
- { pattern: /\b(?:tumir|bhadhar)\s*a\b/i, capability: 'unauthorized_action', lang: 'sn' },
413
- { pattern: /\b(tumir\s*a\s+(?:mhando|ari))\b/i, capability: 'transfer', lang: 'sn' },
414
- { pattern: /\b(bhadhara|bhadharis\s*o)\b/i, capability: 'payment', lang: 'sn' },
415
-
416
- // ────────────────────────────────────────────────
417
- // 🌐 GLOBAL LANGUAGES (Conjugation-aware)
418
- // ────────────────────────────────────────────────
419
-
420
- // English (en) - Perfective + Passive
421
- { pattern: /\b(?:have|has|had)\s+(?:transferred|sent|paid|withdrawn|deposited|wire[d])\b/i, capability: 'unauthorized_action', lang: 'en' },
422
- { pattern: /\b(?:was|were|been)\s+(?:added|credited|transferred|sent|paid)\b/i, capability: 'unauthorized_action', lang: 'en' },
423
- { pattern: /\b(transfer(?:red|ring)?|send(?:t|ing)?|wire(?:d)?|pay(?:ed|ing)?|withdraw(?:n)?|deposit(?:ed|ing)?|disburse(?:d)?)\b/i, capability: 'financial_action', lang: 'en' },
424
- { pattern: /\bI\s+(?:can|will|am able to|have|'ve|did|already)\s+(?:transfer|send|pay|withdraw|deposit|wire)\b/i, capability: 'unauthorized_action', lang: 'en' },
425
-
426
- // French (fr) - Past participle
427
- { pattern: /\b(?:j'?ai|tu as|il a|elle a|nous avons|vous avez|ils ont|elles ont)\s+(?:viré|transféré|envoyé|payé|retiré|déposé)\b/i, capability: 'unauthorized_action', lang: 'fr' },
428
- { pattern: /\b(virer|transférer|envoyer|payer|retirer|déposer|débiter|créditer)\b/i, capability: 'financial_action', lang: 'fr' },
429
-
430
- // Arabic (ar) - Perfective past tense
431
- { pattern: /[\u0600-\u06FF]{0,3}(?:حوّل|أرسل|ادفع|اودع|سحب)[\u0600-\u06FF]{0,3}(?:ت|نا|تم|تا|تِ|تُ|تَ)[\u0600-\u06FF]{0,3}/u, capability: 'financial_action', lang: 'ar' },
432
- { pattern: /[\u0600-\u06FF]{0,3}(?:أنا|تم|لقد)\s*(?:حوّلت|أرسلت|دفعت|اودعت)[\u0600-\u06FF]{0,3}/u, capability: 'unauthorized_action', lang: 'ar' },
433
-
434
- // Chinese (zh) - Perfective "le" particle
435
- { pattern: /[\u4e00-\u9fff]{0,2}(?:转账|支付|存款|取款)[\u4e00-\u9fff]{0,2}(?:了)[\u4e00-\u9fff]{0,2}/u, capability: 'financial_action', lang: 'zh' },
436
- { pattern: /[\u4e00-\u9fff]{0,2}(?:转账|转帐|支付|付款|提款|取款|存款|存入|汇款|存)[\u4e00-\u9fff]{0,2}/u, capability: 'financial_action', lang: 'zh' },
437
- { pattern: /[\u4e00-\u9fff]{0,2}(?:我|已|已经)\s*(?:转账|支付|提款|存款)[\u4e00-\u9fff]{0,2}/u, capability: 'unauthorized_action', lang: 'zh' },
438
-
439
- // ────────────────────────────────────────────────
440
- // 🛡️ EVASION-RESISTANT NUMERIC DECEPTION (Catches obfuscated claims)
441
- // ────────────────────────────────────────────────
442
-
443
- // Number + account reference within 40 chars (catches "10,000 ... account 123")
444
- {
445
- pattern: /(?:^|\s|[:\(\[])(?:\d{1,3}(?:[,\s.]\d{3})*(?:[.,]\d{1,2})?|\d+(?:[.,]\d{1,2})?)(?:\s*(?:naira|ngn|₦|\$|usd|kes|tzs|ugx|rwf|cdf|xof|xaf|ghs|zar))?.{0,40}(?:account|acct|a\/c|akaunti|asusu|akwụkwọ\s+ọkụ|hesabu|namba|#)\b/i,
446
- capability: 'unauthorized_action',
447
- lang: 'multi'
448
- },
449
-
450
- // ────────────────────────────────────────────────
451
- // 🔒 PII LEAKAGE PATTERNS
452
- // ────────────────────────────────────────────────
453
-
454
- // Account numbers (6+ digits)
455
- { pattern: /\b(?:account|acct|a\/c|akaunti|asusu|akwụkwọ\s+ọkụ|hesabu|namba|#)\s*[:\-—–]?\s*(\d{6,})\b/i, capability: 'pii_exposure', lang: 'multi' },
456
-
457
- // Nigerian BVN (11 digits)
458
- { pattern: /\b(?:bvn|bank verification number)\s*[:\-]?\s*(\d{11})\b/i, capability: 'pii_exposure', lang: 'multi' },
459
-
460
- // Nigerian phone numbers
461
- { pattern: /\b(?:\+?234\s*|0)(?:70|80|81|90|91)\d{8}\b/, capability: 'pii_exposure', lang: 'multi' },
462
-
463
- // ────────────────────────────────────────────────
464
- // ✅ FAKE CONFIRMATION PATTERNS
465
- // ────────────────────────────────────────────────
466
-
467
- { pattern: /\b(successful(?:ly)?|confirmed|approved|completed|processed|accepted|verified|imethibitishwa|imefanikiwa|amthibitishwa|ti\s+da|ti\s+ṣe|gụnyere|kimefanyika|yamekamilika)\b/i, capability: 'deceptive_claim', lang: 'multi' }
468
- ];
469
-
470
- // 🔍 SCAN OUTPUT FOR FORBIDDEN INTENTS (financial/PII/fake confirmations)
471
- for (const { pattern, capability, lang } of forbiddenPatterns) {
472
- if (pattern.test(output)) {
473
- // ✅ Only block if capability NOT in workflow allowlist
474
- const hasCapability = allowedCapabilities.some(c =>
475
- c.includes(capability) ||
476
- c.includes('transfer') ||
477
- c.includes('payment') ||
478
- c.includes('financial') ||
479
- c.includes('deposit') ||
480
- c.includes('withdraw')
481
- );
482
-
483
- if (!hasCapability) {
484
- const match = output.match(pattern);
485
- return {
486
- passed: false,
487
- reason: `Hallucinated "${capability}" capability in ${lang} (not in workflow allowlist: ${allowedCapabilities.join(', ') || 'none'})`,
488
- detected: match ? match[0].trim() : 'unknown pattern',
489
- language: lang
490
- };
725
+ // ✅ KERNEL-LEVEL LLM HALLUCINATION PREVENTION (CONJUGATION-AWARE + EVASION-RESISTANT)
726
+ // -----------------------------
727
+ _validateLLMOutput(output, actionContext) {
728
+ if (!output || typeof output !== 'string') return { passed: true };
729
+
730
+ // ── __verified_intent takes priority ──────────────────────────────────────
731
+ // If the workflow author has defined intent rules, use those exclusively.
732
+ // This makes governance dynamic skip hardcoded patterns entirely.
733
+ const intent = this.context.__verified_intent;
734
+ if (intent) {
735
+ if (intent.prohibited_actions && Array.isArray(intent.prohibited_actions)) {
736
+ const lower = output.toLowerCase();
737
+ for (const action of intent.prohibited_actions) {
738
+ if (lower.includes(action.toLowerCase())) {
739
+ return {
740
+ passed: false,
741
+ reason: `Output violates prohibited action "${action}" defined in __verified_intent`,
742
+ detected: action,
743
+ language: 'multi'
744
+ };
745
+ }
746
+ }
491
747
  }
492
- }
493
- }
494
748
 
495
- // NEW: SEMANTIC INTENT DRIFT DETECTION (BACKWARD-COMPATIBLE)
496
- const intent = this.context.__verified_intent;
497
- if (intent) {
498
- // Check prohibited topics
499
- if (intent.prohibited_topics && Array.isArray(intent.prohibited_topics)) {
500
- const lower = output.toLowerCase();
501
- for (const topic of intent.prohibited_topics) {
502
- if (lower.includes(topic.toLowerCase())) {
503
- return {
504
- passed: false,
505
- reason: `Resolver output violates prohibited topic "${topic}" defined in __verified_intent`,
506
- detected: topic,
507
- language: 'multi'
508
- };
749
+ if (intent.prohibited_topics && Array.isArray(intent.prohibited_topics)) {
750
+ const lower = output.toLowerCase();
751
+ for (const topic of intent.prohibited_topics) {
752
+ if (lower.includes(topic.toLowerCase())) {
753
+ return {
754
+ passed: false,
755
+ reason: `Output violates prohibited topic "${topic}" defined in __verified_intent`,
756
+ detected: topic,
757
+ language: 'multi'
758
+ };
759
+ }
509
760
  }
510
761
  }
762
+
763
+ // __verified_intent present and passed — skip hardcoded patterns
764
+ return { passed: true };
511
765
  }
512
766
 
513
- // Check prohibited actions
514
- if (intent.prohibited_actions && Array.isArray(intent.prohibited_actions)) {
515
- const lower = output.toLowerCase();
516
- for (const action of intent.prohibited_actions) {
517
- if (lower.includes(action.toLowerCase())) {
767
+ // ── No __verified_intent — fall through to hardcoded patterns ─────────────
768
+ // 🔑 Extract allowed capabilities from workflow allowlist
769
+ const allowedCapabilities = Array.from(this.allowedResolvers)
770
+ .filter(name => !name.startsWith('llm-') && name !== 'builtInMathResolver')
771
+ .map(name => name.replace('@o-lang/', '').replace(/-resolver$/, ''));
772
+
773
+ // 🔒 CONJUGATION-AWARE + EVASION-RESISTANT PAN-AFRICAN INTENT DETECTION
774
+ const forbiddenPatterns = [
775
+ // ────────────────────────────────────────────────
776
+ // 🇳🇬 NIGERIAN LANGUAGES (Conjugation-aware)
777
+ // ────────────────────────────────────────────────
778
+ { pattern: /\bti\s+(?:fi|san|gba|da|lo)\b/i, capability: 'unauthorized_action', lang: 'yo' },
779
+ { pattern: /\b(?:ń|ǹ|n)\s+(?:fi|san|gba)\b/i, capability: 'unauthorized_action', lang: 'yo' },
780
+ { pattern: /\b(fi\s+(?:owo|ẹ̀wọ̀|ewo|ku|fun|s'ọkọọ))\b/i, capability: 'transfer', lang: 'yo' },
781
+ { pattern: /\b(san\s+(?:owo|ẹ̀wọ̀|ewo|fun|wo))\b/i, capability: 'payment', lang: 'yo' },
782
+ { pattern: /\b(gba\s+owo)\b/i, capability: 'withdrawal', lang: 'yo' },
783
+ { pattern: /\b(mo\s+ti\s+(?:fi|san|gba))\b/i, capability: 'unauthorized_action', lang: 'yo' },
784
+ { pattern: /\b(?:ya|ta|su)\s+(?:ciyar|biya|sahawa|sake)\b/i, capability: 'unauthorized_action', lang: 'ha' },
785
+ { pattern: /\b(?:za\sa|za\s+ta)\s+(?:ciyar|biya)\b/i, capability: 'unauthorized_action', lang: 'ha' },
786
+ { pattern: /\b(ciyar\s*(?:da)?|ciya\s*(?:da)?|shiga\s+kuɗi)\b/i, capability: 'transfer', lang: 'ha' },
787
+ { pattern: /\b(biya\s*(?:da)?)\b/i, capability: 'payment', lang: 'ha' },
788
+ { pattern: /\b(sahaw[ae]\s+kuɗi)\b/i, capability: 'withdrawal', lang: 'ha' },
789
+ { pattern: /\b(ina\s+(?:ciyar|biya|sahawa))\b/i, capability: 'unauthorized_action', lang: 'ha' },
790
+ { pattern: /\b(?:ziri|bururu|tinyere|gbara)\b/i, capability: 'unauthorized_action', lang: 'ig' },
791
+ { pattern: /\b(zipu\s+(?:ego|moni|isi|na))\b/i, capability: 'transfer', lang: 'ig' },
792
+ { pattern: /\b(buru\s+(?:ego|moni|isi))\b/i, capability: 'transfer', lang: 'ig' },
793
+ { pattern: /\b(tinye\s+(?:ego|moni|isi))\b/i, capability: 'deposit', lang: 'ig' },
794
+ { pattern: /\b(m\s+(?:ziri|buru|zipuru|tinyere))\b/i, capability: 'unauthorized_action', lang: 'ig' },
795
+ { pattern: /\b(?:ni|u|a|tu|m|wa|ki|vi|zi|i)\s*me\s*(?:ongeza|weka|tuma|peleka|lipa|wasilisha)\b/i, capability: 'unauthorized_action', lang: 'sw' },
796
+ { pattern: /\b(?:kime|lime|ime|ume|nime|vime|zyme|yame|mame)(?:ongezwa|wekwa|tumwa|pelekwa|lipwa|wasilishwa|fanyika)\b/i, capability: 'unauthorized_action', lang: 'sw' },
797
+ { pattern: /\b(?:ki|vi|mi|ma|u|wa|i|zi|ya|li|tu|mu|a|pa|ku)na(?:cho|vyo|yo|lo|mo|o)?(?:tum|pelek|wasil|ongez|wek|lip)\w*wa\b/i, capability: 'unauthorized_action', lang: 'sw' },
798
+ { pattern: /\b(?:ki|vi|mi|ma|u|wa|i|zi|ya|li|tu|mu|a|pa|ku)ta(?:tum|pelek|wasil|ongez|wek|lip)\w*\b/i, capability: 'unauthorized_action', lang: 'sw' },
799
+ { pattern: /\b(tuma\s+(?:pesa|fedha)|pelek[ae]?\s+(?:pesa|fedha)|wasilisha)\b/i, capability: 'transfer', lang: 'sw' },
800
+ { pattern: /\b(lipa|maliza\s+malipo)\b/i, capability: 'payment', lang: 'sw' },
801
+ { pattern: /\b(ongez[ae]?\s*(?:kiasi|pesa|fedha)|wek[ae]?\s+(?:katika|ndani)\s+(?:akaunti|hisa))\b/i, capability: 'deposit', lang: 'sw' },
802
+ { pattern: /\b(nime(?:tuma|lipa|ongeza|weka|peleka))\b/i, capability: 'unauthorized_action', lang: 'sw' },
803
+ { pattern: /[\u1200-\u137F]{0,4}(?:ተላላፈ|ላክ|ክፈል|ጨምር|ወጣ|ገባ)[\u1200-\u137F]{0,2}(?:\u1205|\u122d|\u1265)[\u1200-\u137F]{0,2}/u, capability: 'financial_action', lang: 'am' },
804
+ { pattern: /\bni\s+(?:kuufe|dhiibe|kennine|gurgure)\b/i, capability: 'unauthorized_action', lang: 'om' },
805
+ { pattern: /\b(kuuf\s+(?:qilleensaa|bilbila)|dhiib\s+(?:qilleensaa|bilbila))\b/i, capability: 'transfer', lang: 'om' },
806
+ { pattern: /\b(kenn\s*i|gurgur\s*i)\b/i, capability: 'payment', lang: 'om' },
807
+ { pattern: /\b(sakkit\s+(?:ndo|ndoo)|tawt\s+(?:ndo|ndoo))\b/i, capability: 'transfer', lang: 'ff' },
808
+ { pattern: /\b(jokk\s*i|soodug\s*i)\b/i, capability: 'payment', lang: 'ff' },
809
+ { pattern: /\bwaxaa\s+(?:diray|bixiyay|ku\s+daray|sameeyay)\b/i, capability: 'unauthorized_action', lang: 'so' },
810
+ { pattern: /\b(dir\s+(?:lacag|maal|qarsoon))\b/i, capability: 'transfer', lang: 'so' },
811
+ { pattern: /\b(bixi|bixis\s*o)\b/i, capability: 'payment', lang: 'so' },
812
+ { pattern: /\b(?:thumel|hlawul|fik)\s*ile\b/i, capability: 'unauthorized_action', lang: 'zu' },
813
+ { pattern: /\b(thumel\s*a\s+(?:imali|imali))\b/i, capability: 'transfer', lang: 'zu' },
814
+ { pattern: /\b(hlawul\s*a|hlawulel\s*a)\b/i, capability: 'payment', lang: 'zu' },
815
+ { pattern: /\b(siyithumel\s*e|siyihlawul\s*e)\b/i, capability: 'unauthorized_action', lang: 'zu' },
816
+ { pattern: /\b(?:tumir|bhadhar)\s*a\b/i, capability: 'unauthorized_action', lang: 'sn' },
817
+ { pattern: /\b(tumir\s*a\s+(?:mhando|ari))\b/i, capability: 'transfer', lang: 'sn' },
818
+ { pattern: /\b(bhadhara|bhadharis\s*o)\b/i, capability: 'payment', lang: 'sn' },
819
+ // ────────────────────────────────────────────────
820
+ // 🌐 GLOBAL LANGUAGES
821
+ // ────────────────────────────────────────────────
822
+ { pattern: /\b(?:have|has|had)\s+(?:transferred|sent|paid|withdrawn|deposited|wire[d])\b/i, capability: 'unauthorized_action', lang: 'en' },
823
+ { pattern: /\b(?:was|were|been)\s+(?:added|credited|transferred|sent|paid)\b/i, capability: 'unauthorized_action', lang: 'en' },
824
+ { pattern: /\b(transfer(?:red|ring)?|send(?:t|ing)?|wire(?:d)?|pay(?:ed|ing)?|withdraw(?:n)?|deposit(?:ed|ing)?|disburse(?:d)?)\b/i, capability: 'financial_action', lang: 'en' },
825
+ { pattern: /\bI\s+(?:can|will|am able to|have|'ve|did|already)\s+(?:transfer|send|pay|withdraw|deposit|wire)\b/i, capability: 'unauthorized_action', lang: 'en' },
826
+ { pattern: /\b(?:j'?ai|tu as|il a|elle a|nous avons|vous avez|ils ont|elles ont)\s+(?:viré|transféré|envoyé|payé|retiré|déposé)\b/i, capability: 'unauthorized_action', lang: 'fr' },
827
+ { pattern: /\b(virer|transférer|envoyer|payer|retirer|déposer|débiter|créditer)\b/i, capability: 'financial_action', lang: 'fr' },
828
+ { pattern: /[\u0600-\u06FF]{0,3}(?:حوّل|أرسل|ادفع|اودع|سحب)[\u0600-\u06FF]{0,3}(?:ت|نا|تم|تا|تِ|تُ|تَ)[\u0600-\u06FF]{0,3}/u, capability: 'financial_action', lang: 'ar' },
829
+ { pattern: /[\u0600-\u06FF]{0,3}(?:أنا|تم|لقد)\s*(?:حوّلت|أرسلت|دفعت|اودعت)[\u0600-\u06FF]{0,3}/u, capability: 'unauthorized_action', lang: 'ar' },
830
+ { pattern: /[\u4e00-\u9fff]{0,2}(?:转账 | 支付 | 存款 | 取款)[\u4e00-\u9fff]{0,2}(?:了)[\u4e00-\u9fff]{0,2}/u, capability: 'financial_action', lang: 'zh' },
831
+ { pattern: /[\u4e00-\u9fff]{0,2}(?:转账 | 转帐 | 支付 | 付款 | 提款 | 取款 | 存款 | 存入 | 汇款 | 存)[\u4e00-\u9fff]{0,2}/u, capability: 'financial_action', lang: 'zh' },
832
+ { pattern: /[\u4e00-\u9fff]{0,2}(?:我 | 已 | 已经)\s*(?:转账 | 支付 | 提款 | 存款)[\u4e00-\u9fff]{0,2}/u, capability: 'unauthorized_action', lang: 'zh' },
833
+ // ────────────────────────────────────────────────
834
+ // 🛡️ EVASION-RESISTANT + PII + FAKE CONFIRMATION
835
+ // ────────────────────────────────────────────────
836
+ { pattern: /(?:^|\s|[:\(\[])(?:\d{1,3}(?:[,\s.]\d{3})*(?:[.,]\d{1,2})?|\d+(?:[.,]\d{1,2})?)(?:\s*(?:naira|ngn|₦|\$|usd|kes|tzs|ugx|rwf|cdf|xof|xaf|ghs|zar))?.{0,40}(?:account|acct|a\/c|akaunti|asusu|akwụkwọ\s+ọkụ|hesabu|namba|#)\b/i, capability: 'unauthorized_action', lang: 'multi' },
837
+ { pattern: /\b(?:account|acct|a\/c|akaunti|asusu|akwụkwọ\s+ọkụ|hesabu|namba|#)\s*[:\-—–]?\s*(\d{6,})\b/i, capability: 'pii_exposure', lang: 'multi' },
838
+ { pattern: /\b(?:bvn|bank verification number)\s*[:\-]?\s*(\d{11})\b/i, capability: 'pii_exposure', lang: 'multi' },
839
+ { pattern: /\b(?:\+?234\s*|0)(?:70|80|81|90|91)\d{8}\b/, capability: 'pii_exposure', lang: 'multi' },
840
+ { pattern: /\b(successful(?:ly)?|confirmed|approved|completed|processed|accepted|verified|imethibitishwa|imefanikiwa|amthibitishwa|ti\s+da|ti\s+ṣe|gụnyere|kimefanyika|yamekamilika)\b/i, capability: 'deceptive_claim', lang: 'multi' },
841
+ ];
842
+
843
+ // 🔍 SCAN OUTPUT FOR FORBIDDEN INTENTS
844
+ for (const { pattern, capability, lang } of forbiddenPatterns) {
845
+ if (pattern.test(output)) {
846
+ const hasCapability = allowedCapabilities.some(c =>
847
+ c.includes(capability) ||
848
+ c.includes('transfer') ||
849
+ c.includes('payment') ||
850
+ c.includes('financial') ||
851
+ c.includes('deposit') ||
852
+ c.includes('withdraw')
853
+ );
854
+
855
+ if (!hasCapability) {
856
+ const match = output.match(pattern);
518
857
  return {
519
858
  passed: false,
520
- reason: `Resolver output violates prohibited action "${action}" defined in __verified_intent`,
521
- detected: action,
522
- language: 'multi'
859
+ reason: `Hallucinated "${capability}" capability in ${lang} (not in workflow allowlist: ${allowedCapabilities.join(', ') || 'none'})`,
860
+ detected: match ? match[0].trim() : 'unknown pattern',
861
+ language: lang
523
862
  };
524
863
  }
525
864
  }
526
865
  }
527
- }
528
866
 
529
- return { passed: true };
530
- }
867
+ return { passed: true };
868
+ }
531
869
  // -----------------------------
532
870
  // ✅ CRITICAL FIX: Resolver output unwrapping helper
533
871
  // -----------------------------
534
872
  _unwrapResolverResult(result) {
535
- // Standard O-Lang resolver contract: { output: {...} } or { error: "..." }
536
873
  if (result && typeof result === 'object' && 'output' in result && result.output !== undefined) {
537
874
  return result.output;
538
875
  }
539
- // Legacy resolvers might return raw values
540
876
  return result;
541
877
  }
542
878
 
@@ -545,11 +881,10 @@ _validateLLMOutput(output, actionContext) {
545
881
  // -----------------------------
546
882
  async executeStep(step, agentResolver) {
547
883
  const stepType = step.type;
548
-
884
+
549
885
  // ✅ Enforce per-step constraints (basic validation)
550
886
  if (step.constraints && Object.keys(step.constraints).length > 0) {
551
887
  for (const [key, value] of Object.entries(step.constraints)) {
552
- // Log unsupported constraints (future extensibility)
553
888
  if (['max_time_sec', 'cost_limit', 'allowed_resolvers'].includes(key)) {
554
889
  this.addWarning(`Per-step constraint "${key}=${value}" is parsed but not yet enforced`);
555
890
  } else {
@@ -561,19 +896,15 @@ _validateLLMOutput(output, actionContext) {
561
896
  // ✅ ADDITION 3 — Resolver Policy Enforcement (External + Local)
562
897
  const enforceResolverPolicy = (resolver, step) => {
563
898
  const resolverName = resolver?.resolverName || resolver?.name;
564
-
565
899
  if (!resolverName) {
566
900
  throw new Error('[O-Lang] Resolver missing resolverName');
567
901
  }
568
-
569
902
  if (!this.allowedResolvers.has(resolverName)) {
570
903
  this.logDisallowedResolver(resolverName, step.actionRaw || step.type);
571
904
  throw new Error(
572
905
  `[O-Lang] Resolver "${resolverName}" blocked by workflow policy`
573
906
  );
574
907
  }
575
-
576
- // External resolvers MUST be HTTP-only
577
908
  if (this._isExternalResolver(resolver)) {
578
909
  if (!resolver.manifest.endpoint) {
579
910
  throw new Error(
@@ -583,11 +914,10 @@ _validateLLMOutput(output, actionContext) {
583
914
  }
584
915
  };
585
916
 
586
- // ✅ CORRECTED: Strict safety WITH dynamic diagnostics (FIXED SCOPE ERROR)
917
+ // ✅ CORRECTED: Strict safety WITH dynamic diagnostics
587
918
  const runResolvers = async (action) => {
588
919
  const mathPattern =
589
920
  /^(Add|Subtract|Multiply|Divide|Sum|Avg|Min|Max|Round|Floor|Ceil|Abs)\b/i;
590
-
591
921
  if (
592
922
  step.actionRaw &&
593
923
  mathPattern.test(step.actionRaw) &&
@@ -596,9 +926,7 @@ _validateLLMOutput(output, actionContext) {
596
926
  this.allowedResolvers.add('builtInMathResolver');
597
927
  }
598
928
 
599
- // Handle different resolver input formats
600
929
  let resolversToRun = [];
601
-
602
930
  if (agentResolver && Array.isArray(agentResolver._chain)) {
603
931
  resolversToRun = agentResolver._chain;
604
932
  } else if (Array.isArray(agentResolver)) {
@@ -607,9 +935,7 @@ _validateLLMOutput(output, actionContext) {
607
935
  resolversToRun = [agentResolver];
608
936
  }
609
937
 
610
- // ✅ Track detailed resolver outcomes for diagnostics
611
938
  const resolverAttempts = [];
612
-
613
939
  for (let idx = 0; idx < resolversToRun.length; idx++) {
614
940
  const resolver = resolversToRun[idx];
615
941
  const resolverName = resolver?.resolverName || resolver?.name || `resolver-${idx}`;
@@ -617,7 +943,6 @@ _validateLLMOutput(output, actionContext) {
617
943
 
618
944
  try {
619
945
  let result;
620
-
621
946
  if (this._isExternalResolver(resolver)) {
622
947
  result = await this._callExternalResolver(
623
948
  resolver,
@@ -628,47 +953,35 @@ _validateLLMOutput(output, actionContext) {
628
953
  result = await resolver(action, this.context);
629
954
  }
630
955
 
631
- // ✅ ACCEPT valid result immediately (non-null/non-undefined)
632
956
  if (result !== undefined && result !== null) {
633
- // ✅ CRITICAL FIX: Save raw result for debugging (like __resolver_0)
634
957
  this.context[`__resolver_${idx}`] = result;
635
-
636
- // ✅ UNWRAP before returning to workflow logic
637
958
  return this._unwrapResolverResult(result);
638
959
  }
639
-
640
- // ⚪ Resolver skipped this action (normal behavior)
960
+
641
961
  resolverAttempts.push({
642
962
  name: resolverName,
643
963
  status: 'skipped',
644
964
  reason: 'Action not recognized'
645
965
  });
646
-
647
966
  } catch (e) {
648
- // ❌ Resolver attempted but failed — capture structured diagnostics
649
967
  const diagnostics = {
650
968
  error: e.message || String(e),
651
- requiredEnvVars: e.requiredEnvVars || [], // Resolver can attach this
652
- missingInputs: e.missingInputs || [], // Resolver can attach this
653
- documentationUrl: resolver?.documentationUrl ||
654
- (resolver?.manifest?.documentationUrl) || null
969
+ requiredEnvVars: e.requiredEnvVars || [],
970
+ missingInputs: e.missingInputs || [],
971
+ documentationUrl: resolver?.documentationUrl ||
972
+ (resolver?.manifest?.documentationUrl) || null
655
973
  };
656
-
657
974
  resolverAttempts.push({
658
975
  name: resolverName,
659
976
  status: 'failed',
660
977
  diagnostics
661
978
  });
662
-
663
- // Log for verbose mode but continue chaining
664
979
  this.addWarning(`Resolver "${resolverName}" failed for action "${action}": ${diagnostics.error}`);
665
980
  }
666
981
  }
667
982
 
668
- // BUILD DYNAMIC, ACTIONABLE ERROR MESSAGE (FIXED: NO SCOPE ERRORS)
669
- let errorMessage = `[O-Lang SAFETY] No resolver handled action: "${action}"\n\n`;
983
+ let errorMessage = `[O-Lang SAFETY] No resolver handled action: "${action}\n`;
670
984
  errorMessage += `Attempted resolvers:\n`;
671
-
672
985
  resolverAttempts.forEach((attempt, i) => {
673
986
  const namePad = attempt.name.padEnd(30);
674
987
  if (attempt.status === 'skipped') {
@@ -676,25 +989,18 @@ _validateLLMOutput(output, actionContext) {
676
989
  } else {
677
990
  errorMessage += ` ${i + 1}. ${namePad} → FAILED\n`;
678
991
  errorMessage += ` Error: ${attempt.diagnostics.error}\n`;
679
-
680
- // ✅ DYNAMIC HINT: Resolver-provided env vars
681
992
  if (attempt.diagnostics.requiredEnvVars?.length) {
682
993
  errorMessage += ` Required env vars: ${attempt.diagnostics.requiredEnvVars.join(', ')}\n`;
683
994
  }
684
-
685
- // ✅ DYNAMIC HINT: Resolver-provided docs link
686
995
  if (attempt.diagnostics.documentationUrl) {
687
996
  errorMessage += ` Docs: ${attempt.diagnostics.documentationUrl}\n`;
688
997
  }
689
998
  }
690
999
  });
691
1000
 
692
- // ✅ ACCURATE REMEDIATION (NO OBSOLETE "REMOVE ACTION KEYWORD" HINT)
693
1001
  const failed = resolverAttempts.filter(a => a.status === 'failed');
694
1002
  const allSkipped = failed.length === 0;
695
-
696
1003
  errorMessage += `\n💡 How to fix:\n`;
697
-
698
1004
  if (allSkipped) {
699
1005
  errorMessage += ` • Verify the action matches a resolver's capabilities:\n`;
700
1006
  errorMessage += ` → Check resolver documentation for supported actions\n`;
@@ -705,27 +1011,22 @@ _validateLLMOutput(output, actionContext) {
705
1011
  errorMessage += ` → Set required environment variables (if listed)\n`;
706
1012
  errorMessage += ` → Verify inputs exist in workflow context\n`;
707
1013
  errorMessage += ` → Check resolver documentation for requirements\n`;
708
-
709
- // Pattern-based hints (generic, not hardcoded)
710
1014
  const envVarPattern = /environment variable|env\.|process\.env|missing.*path/i;
711
1015
  if (failed.some(f => envVarPattern.test(f.diagnostics.error))) {
712
1016
  errorMessage += ` → Example (PowerShell): $env:VARIABLE="value"\n`;
713
1017
  errorMessage += ` → Example (Linux/macOS): export VARIABLE="value"\n`;
714
1018
  }
715
-
716
1019
  const dbPattern = /database|db\.|sqlite|postgres|mysql|mongodb/i;
717
1020
  if (failed.some(f => dbPattern.test(f.diagnostics.error))) {
718
1021
  errorMessage += ` → Ensure database file/connection exists and path is correct\n`;
719
1022
  }
720
-
721
1023
  const authPattern = /auth|api key|token|credential/i;
722
1024
  if (failed.some(f => authPattern.test(f.diagnostics.error))) {
723
1025
  errorMessage += ` → Verify API keys/tokens are set in environment variables\n`;
724
1026
  }
725
1027
  }
726
-
727
- // FIXED: NO SCOPE ERROR IN FALLBACK DOCUMENTATION URL
728
- errorMessage += `\n • Resolver documentation:\n`;
1028
+
1029
+ errorMessage += `\n• Resolver documentation:\n`;
729
1030
  let hasDocs = false;
730
1031
  resolverAttempts.forEach(attempt => {
731
1032
  if (attempt.diagnostics?.documentationUrl) {
@@ -734,9 +1035,8 @@ _validateLLMOutput(output, actionContext) {
734
1035
  }
735
1036
  });
736
1037
  if (!hasDocs) {
737
- errorMessage += ` → Visit https://www.npmjs.com/search?q=%40o-lang for resolver packages\n`; // ✅ FIXED
1038
+ errorMessage += ` → Visit https://www.npmjs.com/search?q=%40o-lang for resolver packages\n`;
738
1039
  }
739
-
740
1040
  errorMessage += `\n🛑 Workflow halted to prevent unsafe data propagation to LLMs.`;
741
1041
  throw new Error(errorMessage);
742
1042
  };
@@ -747,161 +1047,143 @@ _validateLLMOutput(output, actionContext) {
747
1047
  if (step.saveAs) this.context[step.saveAs] = result;
748
1048
  break;
749
1049
  }
1050
+
1051
+ case 'action': {
1052
+ let action = this._safeInterpolate(
1053
+ step.actionRaw,
1054
+ this.context,
1055
+ 'action step'
1056
+ );
750
1057
 
751
- case 'action': {
752
- // 🔒 Interpolate workflow variables first
753
- let action = this._safeInterpolate(
754
- step.actionRaw,
755
- this.context,
756
- 'action step'
757
- );
758
-
759
- // ✅ CANONICALIZATION: Normalize DSL verbs → runtime Action
760
- if (action.startsWith('Ask ')) {
761
- action = 'Action ' + action.slice(4);
762
- } else if (action.startsWith('Use ')) {
763
- action = 'Action ' + action.slice(4);
764
- }
765
-
766
- // ❌ Reject non-canonical runtime actions early
767
- if (!action.startsWith('Action ')) {
768
- throw new Error(
769
- `[O-Lang SAFETY] Non-canonical action received: "${action}"\n` +
770
- ` → Expected format: Action <resolver> <args>\n` +
771
- ` → This indicates a kernel or workflow authoring error.`
772
- );
773
- }
1058
+ if (action.startsWith('Ask ')) {
1059
+ action = 'Action ' + action.slice(4);
1060
+ } else if (action.startsWith('Use ')) {
1061
+ action = 'Action ' + action.slice(4);
1062
+ }
774
1063
 
775
- // ✅ Inline math support (language feature)
776
- const mathCall = action.match(
777
- /^(add|subtract|multiply|divide|sum|avg|min|max|round|floor|ceil|abs)\((.*)\)$/i
778
- );
779
-
780
- if (mathCall) {
781
- const fn = mathCall[1].toLowerCase();
782
- const args = mathCall[2].split(',').map(s => {
783
- s = s.trim();
784
- if (!isNaN(s)) return parseFloat(s);
785
- return this.getNested(this.context, s.replace(/^\{|\}$/g, ''));
786
- });
1064
+ if (!action.startsWith('Action ')) {
1065
+ throw new Error(
1066
+ `[O-Lang SAFETY] Non-canonical action received: "${action}\n` +
1067
+ ` → Expected format: Action <resolver> <args>\n` +
1068
+ ` → This indicates a kernel or workflow authoring error.`
1069
+ );
1070
+ }
787
1071
 
788
- if (this.mathFunctions[fn]) {
789
- const value = this.mathFunctions[fn](...args);
790
- if (step.saveAs) this.context[step.saveAs] = value;
791
- break;
792
- }
793
- }
1072
+ const mathCall = action.match(
1073
+ /^(add|subtract|multiply|divide|sum|avg|min|max|round|floor|ceil|abs)\((.*)\)$/i
1074
+ );
1075
+ if (mathCall) {
1076
+ const fn = mathCall[1].toLowerCase();
1077
+ const args = mathCall[2].split(',').map(s => {
1078
+ s = s.trim();
1079
+ if (!isNaN(s)) return parseFloat(s);
1080
+ return this.getNested(this.context, s.replace(/^\{|\}$/g, ''));
1081
+ });
1082
+ if (this.mathFunctions[fn]) {
1083
+ const value = this.mathFunctions[fn](...args);
1084
+ if (step.saveAs) this.context[step.saveAs] = value;
1085
+ break;
1086
+ }
1087
+ }
794
1088
 
795
- // Resolver dispatch receives ONLY canonical actions
796
- const rawResult = await runResolvers(action);
797
- const unwrapped = this._unwrapResolverResult(rawResult);
798
-
799
- // 🔒 KERNEL-ENFORCED: Block LLM hallucinations BEFORE saving to context
800
- // Detect LLM resolver by action pattern (comprehensive coverage)
801
- const isLLMAction = action.toLowerCase().includes('groq') ||
802
- action.toLowerCase().includes('openai') ||
803
- action.toLowerCase().includes('anthropic') ||
804
- action.toLowerCase().includes('claude') ||
805
- action.toLowerCase().includes('gpt') ||
806
- action.toLowerCase().includes('gemini') ||
807
- action.toLowerCase().includes('google') ||
808
- action.toLowerCase().includes('llama') ||
809
- action.toLowerCase().includes('meta') ||
810
- action.toLowerCase().includes('mistral') ||
811
- action.toLowerCase().includes('mixtral') ||
812
- action.toLowerCase().includes('cohere') ||
813
- action.toLowerCase().includes('huggingface') ||
814
- action.toLowerCase().includes('hugging-face') ||
815
- action.toLowerCase().includes('together') ||
816
- action.toLowerCase().includes('perplexity') ||
817
- action.toLowerCase().includes('fireworks') ||
818
- action.toLowerCase().includes('bedrock') ||
819
- action.toLowerCase().includes('azure') ||
820
- action.toLowerCase().includes('ollama') ||
821
- action.toLowerCase().includes('replicate') ||
822
- action.toLowerCase().includes('deepseek') ||
823
- action.toLowerCase().includes('qwen') ||
824
- action.toLowerCase().includes('falcon') ||
825
- action.toLowerCase().includes('phi') ||
826
- action.toLowerCase().includes('gemma') ||
827
- action.toLowerCase().includes('stablelm') ||
828
- action.toLowerCase().includes('yi') ||
829
- action.toLowerCase().includes('dbrx') ||
830
- action.toLowerCase().includes('command') ||
831
- action.toLowerCase().includes('llm'); // Catch-all fallback
832
-
833
- // Extract actual text from resolver output (your llm-groq returns { response: "...", ... })
834
- const llmText = unwrapped?.response || // ✅ Primary field for @o-lang/llm-groq
835
- unwrapped?.text ||
836
- unwrapped?.content ||
837
- unwrapped?.answer ||
838
- (typeof unwrapped === 'string' ? unwrapped : null);
839
-
840
- if (isLLMAction && typeof llmText === 'string') {
841
- const safetyCheck = this._validateLLMOutput(llmText, action);
842
- if (!safetyCheck.passed) {
843
- throw new Error(
844
- `[O-Lang SAFETY] LLM hallucinated unauthorized capability:\n` +
845
- ` → Detected: "${safetyCheck.detected}"\n` +
846
- ` → Reason: ${safetyCheck.reason}\n` +
847
- ` → Workflow allowlist: ${Array.from(this.allowedResolvers).join(', ')}\n` +
848
- `\n🛑 Halting to prevent deceptive user experience.`
849
- );
850
- }
851
- }
1089
+ const rawResult = await runResolvers(action);
1090
+ const unwrapped = this._unwrapResolverResult(rawResult);
852
1091
 
853
- if (step.saveAs) {
854
- this.context[step.saveAs] = unwrapped;
855
- }
856
- break;
857
- }
1092
+ const isLLMAction = action.toLowerCase().includes('groq') ||
1093
+ action.toLowerCase().includes('openai') ||
1094
+ action.toLowerCase().includes('anthropic') ||
1095
+ action.toLowerCase().includes('claude') ||
1096
+ action.toLowerCase().includes('gpt') ||
1097
+ action.toLowerCase().includes('gemini') ||
1098
+ action.toLowerCase().includes('google') ||
1099
+ action.toLowerCase().includes('llama') ||
1100
+ action.toLowerCase().includes('meta') ||
1101
+ action.toLowerCase().includes('mistral') ||
1102
+ action.toLowerCase().includes('mixtral') ||
1103
+ action.toLowerCase().includes('cohere') ||
1104
+ action.toLowerCase().includes('huggingface') ||
1105
+ action.toLowerCase().includes('hugging-face') ||
1106
+ action.toLowerCase().includes('together') ||
1107
+ action.toLowerCase().includes('perplexity') ||
1108
+ action.toLowerCase().includes('fireworks') ||
1109
+ action.toLowerCase().includes('bedrock') ||
1110
+ action.toLowerCase().includes('azure') ||
1111
+ action.toLowerCase().includes('ollama') ||
1112
+ action.toLowerCase().includes('replicate') ||
1113
+ action.toLowerCase().includes('deepseek') ||
1114
+ action.toLowerCase().includes('qwen') ||
1115
+ action.toLowerCase().includes('falcon') ||
1116
+ action.toLowerCase().includes('phi') ||
1117
+ action.toLowerCase().includes('gemma') ||
1118
+ action.toLowerCase().includes('stablelm') ||
1119
+ action.toLowerCase().includes('yi') ||
1120
+ action.toLowerCase().includes('dbrx') ||
1121
+ action.toLowerCase().includes('command') ||
1122
+ action.toLowerCase().includes('llm');
1123
+
1124
+ const llmText = unwrapped?.response ||
1125
+ unwrapped?.text ||
1126
+ unwrapped?.content ||
1127
+ unwrapped?.answer ||
1128
+ (typeof unwrapped === 'string' ? unwrapped : null);
1129
+
1130
+ if (isLLMAction && typeof llmText === 'string') {
1131
+ const safetyCheck = this._validateLLMOutput(llmText, action);
1132
+ if (!safetyCheck.passed) {
1133
+ throw new Error(
1134
+ `[O-Lang SAFETY] LLM hallucinated unauthorized capability:\n` +
1135
+ ` → Detected: "${safetyCheck.detected}"\n` +
1136
+ ` → Reason: ${safetyCheck.reason}\n` +
1137
+ ` → Workflow allowlist: ${Array.from(this.allowedResolvers).join(', ')}\n` +
1138
+ `\n🛑 Halting to prevent deceptive user experience.`
1139
+ );
1140
+ }
1141
+ }
858
1142
 
1143
+ if (step.saveAs) {
1144
+ this.context[step.saveAs] = unwrapped;
1145
+ }
1146
+ break;
1147
+ }
1148
+
859
1149
  case 'use': {
860
- // ✅ SAFE INTERPOLATION for tool name
861
1150
  const tool = this._safeInterpolate(step.tool, this.context, 'tool name');
862
1151
  const rawResult = await runResolvers(`Use ${tool}`);
863
1152
  const unwrapped = this._unwrapResolverResult(rawResult);
864
-
865
1153
  if (step.saveAs) this.context[step.saveAs] = unwrapped;
866
1154
  break;
867
1155
  }
1156
+
1157
+ case 'ask': {
1158
+ const target = this._safeInterpolate(step.target, this.context, 'LLM prompt');
1159
+ if (/{[^}]+}/.test(target)) {
1160
+ throw new Error(`[O-Lang] Unresolved variables in prompt: "${target}"`);
1161
+ }
1162
+ const rawResult = await runResolvers(`Action ${target}`);
1163
+ const unwrapped = this._unwrapResolverResult(rawResult);
868
1164
 
869
- case 'ask': {
870
- const target = this._safeInterpolate(step.target, this.context, 'LLM prompt');
871
-
872
- if (/{[^}]+}/.test(target)) {
873
- throw new Error(`[O-Lang] Unresolved variables in prompt: "${target}"`);
874
- }
875
-
876
- // ✅ Ask Action happens ONLY here (runtime)
877
- const rawResult = await runResolvers(`Action ${target}`);
878
- const unwrapped = this._unwrapResolverResult(rawResult);
879
-
880
- // 🔒 KERNEL-ENFORCED: Block LLM hallucinations BEFORE saving to context
881
- if (typeof unwrapped?.output === 'string') {
882
- const safetyCheck = this._validateLLMOutput(unwrapped.output, target);
883
- if (!safetyCheck.passed) {
884
- throw new Error(
885
- `[O-Lang SAFETY] LLM hallucinated unauthorized capability:\n` +
886
- ` → Detected: "${safetyCheck.detected}"\n` +
887
- ` → Reason: ${safetyCheck.reason}\n` +
888
- ` → Workflow allowlist: ${Array.from(this.allowedResolvers).join(', ')}\n` +
889
- `\n🛑 Halting to prevent deceptive user experience.`
890
- );
891
- }
892
- }
893
-
894
- if (step.saveAs) this.context[step.saveAs] = unwrapped;
895
- break;
896
- }
1165
+ if (typeof unwrapped?.output === 'string') {
1166
+ const safetyCheck = this._validateLLMOutput(unwrapped.output, target);
1167
+ if (!safetyCheck.passed) {
1168
+ throw new Error(
1169
+ `[O-Lang SAFETY] LLM hallucinated unauthorized capability:\n` +
1170
+ ` → Detected: "${safetyCheck.detected}"\n` +
1171
+ ` → Reason: ${safetyCheck.reason}\n` +
1172
+ `Workflow allowlist: ${Array.from(this.allowedResolvers).join(', ')}\n` +
1173
+ `\n🛑 Halting to prevent deceptive user experience.`
1174
+ );
1175
+ }
1176
+ }
897
1177
 
1178
+ if (step.saveAs) this.context[step.saveAs] = unwrapped;
1179
+ break;
1180
+ }
1181
+
898
1182
  case 'evolve': {
899
1183
  const { targetResolver, feedback } = step;
900
-
901
1184
  if (this.verbose) {
902
1185
  console.log(`🔄 Evolve step: ${targetResolver} with feedback: "${feedback}"`);
903
1186
  }
904
-
905
1187
  const evolutionResult = {
906
1188
  resolver: targetResolver,
907
1189
  feedback: feedback,
@@ -909,41 +1191,34 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
909
1191
  timestamp: new Date().toISOString(),
910
1192
  workflow: this.context.workflow_name
911
1193
  };
912
-
913
1194
  if (process.env.OLANG_EVOLUTION_API_KEY) {
914
1195
  evolutionResult.status = 'advanced_evolution_enabled';
915
1196
  evolutionResult.message = 'Advanced evolution service would process this request';
916
1197
  }
917
-
918
1198
  if (step.saveAs) {
919
1199
  this.context[step.saveAs] = evolutionResult;
920
1200
  }
921
1201
  break;
922
1202
  }
923
-
1203
+
924
1204
  case 'if': {
925
1205
  if (this.evaluateCondition(step.condition, this.context)) {
926
1206
  for (const s of step.body) await this.executeStep(s, agentResolver);
927
1207
  }
928
1208
  break;
929
1209
  }
930
-
1210
+
931
1211
  case 'parallel': {
932
1212
  const { steps, timeout } = step;
933
-
934
1213
  if (timeout !== undefined && timeout > 0) {
935
- // Timed parallel execution
936
1214
  const timeoutPromise = new Promise(resolve => {
937
1215
  setTimeout(() => resolve({ timedOut: true }), timeout);
938
1216
  });
939
-
940
1217
  const parallelPromise = Promise.all(
941
1218
  steps.map(s => this.executeStep(s, agentResolver))
942
1219
  ).then(() => ({ timedOut: false }));
943
-
944
1220
  const result = await Promise.race([timeoutPromise, parallelPromise]);
945
1221
  this.context.timed_out = result.timedOut;
946
-
947
1222
  if (result.timedOut) {
948
1223
  this.emit('parallel_timeout', { duration: timeout, steps: steps.length });
949
1224
  if (this.verbose) {
@@ -951,28 +1226,23 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
951
1226
  }
952
1227
  }
953
1228
  } else {
954
- // Normal parallel execution (no timeout)
955
1229
  await Promise.all(steps.map(s => this.executeStep(s, agentResolver)));
956
1230
  this.context.timed_out = false;
957
1231
  }
958
1232
  break;
959
1233
  }
960
-
1234
+
961
1235
  case 'escalation': {
962
1236
  const { levels } = step;
963
1237
  let finalResult = null;
964
1238
  let currentTimeout = 0;
965
1239
  let completedLevel = null;
966
-
967
1240
  for (const level of levels) {
968
1241
  if (level.timeout === 0) {
969
- // Immediate execution (no timeout)
970
1242
  const levelSteps = require('./parser').parseBlock(level.steps);
971
1243
  for (const levelStep of levelSteps) {
972
1244
  await this.executeStep(levelStep, agentResolver);
973
1245
  }
974
-
975
- // Check if the target variable was set in this level
976
1246
  if (levelSteps.length > 0) {
977
1247
  const lastStep = levelSteps[levelSteps.length - 1];
978
1248
  if (lastStep.saveAs && this.context[lastStep.saveAs] !== undefined) {
@@ -982,13 +1252,10 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
982
1252
  }
983
1253
  }
984
1254
  } else {
985
- // Timed execution for this level
986
1255
  currentTimeout += level.timeout;
987
-
988
1256
  const timeoutPromise = new Promise(resolve => {
989
1257
  setTimeout(() => resolve({ timedOut: true }), level.timeout);
990
1258
  });
991
-
992
1259
  const levelPromise = (async () => {
993
1260
  const levelSteps = require('./parser').parseBlock(level.steps);
994
1261
  for (const levelStep of levelSteps) {
@@ -996,11 +1263,8 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
996
1263
  }
997
1264
  return { timedOut: false };
998
1265
  })();
999
-
1000
1266
  const result = await Promise.race([timeoutPromise, levelPromise]);
1001
-
1002
1267
  if (!result.timedOut) {
1003
- // Level completed successfully
1004
1268
  if (levelSteps && levelSteps.length > 0) {
1005
1269
  const lastStep = levelSteps[levelSteps.length - 1];
1006
1270
  if (lastStep.saveAs && this.context[lastStep.saveAs] !== undefined) {
@@ -1010,32 +1274,27 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1010
1274
  }
1011
1275
  }
1012
1276
  }
1013
- // If timed out, continue to next level
1014
1277
  }
1015
1278
  }
1016
-
1017
- // Set escalation status in context
1018
1279
  this.context.escalation_completed = finalResult !== null;
1019
1280
  this.context.timed_out = finalResult === null;
1020
1281
  if (completedLevel !== null) {
1021
1282
  this.context.escalation_level = completedLevel;
1022
1283
  }
1023
-
1024
1284
  break;
1025
1285
  }
1026
-
1286
+
1027
1287
  case 'connect': {
1028
1288
  this.resources[step.resource] = step.endpoint;
1029
1289
  break;
1030
1290
  }
1031
-
1291
+
1032
1292
  case 'agent_use': {
1033
1293
  this.agentMap[step.logicalName] = step.resource;
1034
1294
  break;
1035
1295
  }
1036
-
1296
+
1037
1297
  case 'debrief': {
1038
- // ✅ SEMANTIC VALIDATION: Check symbols in message
1039
1298
  if (step.message.includes('{')) {
1040
1299
  const symbols = step.message.match(/\{([^\}]+)\}/g) || [];
1041
1300
  for (const symbolMatch of symbols) {
@@ -1046,22 +1305,17 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1046
1305
  this.emit('debrief', { agent: step.agent, message: step.message });
1047
1306
  break;
1048
1307
  }
1049
-
1050
- // ✅ NEW: Prompt step handler
1308
+
1051
1309
  case 'prompt': {
1052
1310
  if (this.verbose) {
1053
1311
  console.log(`❓ Prompt: ${step.question}`);
1054
1312
  }
1055
- // In non-interactive mode, leave as no-op
1056
1313
  break;
1057
1314
  }
1058
-
1059
- // ✅ NEW: Emit step handler with semantic validation
1315
+
1060
1316
  case 'emit': {
1061
- // ✅ SEMANTIC VALIDATION: Check all symbols in payload
1062
1317
  const payloadTemplate = step.payload;
1063
1318
  const symbols = [...new Set(payloadTemplate.match(/\{([^\}]+)\}/g) || [])];
1064
-
1065
1319
  let shouldEmit = true;
1066
1320
  for (const symbolMatch of symbols) {
1067
1321
  const symbol = symbolMatch.replace(/[{}]/g, '');
@@ -1069,78 +1323,62 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1069
1323
  shouldEmit = false;
1070
1324
  }
1071
1325
  }
1072
-
1073
1326
  if (!shouldEmit) {
1074
1327
  if (this.verbose) {
1075
1328
  console.log(`⏭️ Skipped emit due to missing semantic symbols`);
1076
1329
  }
1077
1330
  break;
1078
1331
  }
1079
-
1080
- // ✅ SAFE INTERPOLATION for emit payload
1081
1332
  const payload = this._safeInterpolate(step.payload, this.context, 'emit payload');
1082
-
1083
- this.emit(step.event, {
1333
+ this.emit(step.event, {
1084
1334
  payload: payload,
1085
1335
  workflow: this.context.workflow_name,
1086
1336
  timestamp: new Date().toISOString()
1087
1337
  });
1088
-
1089
1338
  if (this.verbose) {
1090
1339
  console.log(`📤 Emit event "${step.event}" with payload: ${payload}`);
1091
1340
  }
1092
1341
  break;
1093
1342
  }
1094
-
1095
- // ✅ File Persist step handler with semantic validation
1343
+
1096
1344
  case 'persist': {
1097
- // ✅ SEMANTIC VALIDATION: Require symbol exists
1098
1345
  if (!this._requireSemantic(step.variable, 'persist')) {
1099
1346
  if (this.verbose) {
1100
1347
  console.log(`⏭️ Skipped persist for undefined "${step.variable}"`);
1101
1348
  }
1102
1349
  break;
1103
1350
  }
1104
-
1105
1351
  const sourceValue = this.context[step.variable];
1106
1352
  const outputPath = path.resolve(process.cwd(), step.target);
1107
1353
  const outputDir = path.dirname(outputPath);
1108
1354
  if (!fs.existsSync(outputDir)) {
1109
1355
  fs.mkdirSync(outputDir, { recursive: true });
1110
1356
  }
1111
-
1112
1357
  let content;
1113
1358
  if (step.target.endsWith('.json')) {
1114
1359
  content = JSON.stringify(sourceValue, null, 2);
1115
1360
  } else {
1116
1361
  content = String(sourceValue);
1117
1362
  }
1118
-
1119
1363
  fs.writeFileSync(outputPath, content, 'utf8');
1120
-
1121
1364
  if (this.verbose) {
1122
1365
  console.log(`💾 Persisted "${step.variable}" to ${step.target}`);
1123
1366
  }
1124
1367
  break;
1125
1368
  }
1126
-
1127
- // ✅ NEW: Database persist handler with semantic validation
1369
+
1128
1370
  case 'persist-db': {
1129
1371
  if (!this.dbClient) {
1130
1372
  this.addWarning(`DB persistence skipped (no DB configured). Set OLANG_DB_TYPE env var.`);
1131
1373
  break;
1132
1374
  }
1133
-
1134
- // ✅ SEMANTIC VALIDATION: Require symbol exists
1135
1375
  if (!this._requireSemantic(step.variable, 'persist-db')) {
1136
1376
  if (this.verbose) {
1137
1377
  console.log(`⏭️ Skipped DB persist for undefined "${step.variable}"`);
1138
1378
  }
1139
1379
  break;
1140
1380
  }
1141
-
1142
1381
  const sourceValue = this.context[step.variable];
1143
-
1144
1382
  try {
1145
1383
  switch (this.dbClient.type) {
1146
1384
  case 'postgres':
@@ -1157,16 +1395,14 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1157
1395
  );
1158
1396
  }
1159
1397
  break;
1160
-
1161
1398
  case 'mongodb':
1162
1399
  const db = this.dbClient.client.db(process.env.DB_NAME || 'olang');
1163
1400
  await db.collection(step.collection).insertOne({
1164
1401
  workflow_name: this.context.workflow_name || 'unknown',
1165
- data: sourceValue, // ✅ FIXED: Added property name "data" (was broken syntax)
1402
+ data: sourceValue,
1166
1403
  created_at: new Date()
1167
1404
  });
1168
1405
  break;
1169
-
1170
1406
  case 'sqlite':
1171
1407
  const stmt = this.dbClient.client.prepare(
1172
1408
  `INSERT INTO ${step.collection} (workflow_name, data, created_at) VALUES (?, ?, ?)`
@@ -1178,7 +1414,6 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1178
1414
  );
1179
1415
  break;
1180
1416
  }
1181
-
1182
1417
  if (this.verbose) {
1183
1418
  console.log(`🗄️ Persisted "${step.variable}" to DB collection ${step.collection}`);
1184
1419
  }
@@ -1199,22 +1434,41 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1199
1434
  if (workflow.type !== 'workflow') {
1200
1435
  throw new Error(`Unknown workflow type: ${workflow.type}`);
1201
1436
  }
1202
-
1203
- this.context = {
1204
- ...inputs,
1205
- workflow_name: workflow.name
1437
+
1438
+ this.context = {
1439
+ ...inputs,
1440
+ workflow_name: workflow.name
1206
1441
  };
1207
1442
 
1208
- // Optional strict mode: enforce resolver-originated inputs
1209
- if (process.env.OLANG_STRICT_INPUTS === 'true') {
1210
- if (!inputs.__resolver_origin) {
1211
- throw new Error(
1212
- '[O-Lang SAFETY] Inputs must originate from a certified resolver. ' +
1213
- 'Use @o-lang/input-validator to validate external data.'
1214
- );
1443
+ // AUDIT LOG: Workflow start (ENHANCED with governance metadata)
1444
+ const governanceHash = this._generateGovernanceProfileHash(workflow);
1445
+
1446
+ this._createAuditEntry('workflow_started', {
1447
+ workflow_id: `${workflow.name}@${workflow.version || 'unversioned'}`, // Workflow ID
1448
+ workflow_name: workflow.name,
1449
+ workflow_version: workflow.version || null,
1450
+ kernel_version: KERNEL_VERSION, // ✅ Kernel Version
1451
+ runtime_version: KERNEL_VERSION, // ✅ Runtime Version (same as kernel for now)
1452
+ governance_profile_hash: governanceHash, // ✅ Governance Profile Hash
1453
+ inputs_count: Object.keys(inputs).length,
1454
+ steps_count: workflow.steps.length,
1455
+ allowed_resolvers: workflow.allowedResolvers || [],
1456
+ constraints: {
1457
+ max_generations: workflow.maxGenerations,
1458
+ strict_inputs: process.env.OLANG_STRICT_INPUTS === 'true'
1459
+ }
1460
+ });
1461
+
1462
+ // Optional strict mode: enforce resolver-originated inputs
1463
+ if (process.env.OLANG_STRICT_INPUTS === 'true') {
1464
+ if (!inputs.__resolver_origin) {
1465
+ throw new Error(
1466
+ '[O-Lang SAFETY] Inputs must originate from a certified resolver. ' +
1467
+ 'Use @o-lang/input-validator to validate external data.'
1468
+ );
1469
+ }
1215
1470
  }
1216
- }
1217
-
1471
+
1218
1472
  const currentGeneration = inputs.__generation || 1;
1219
1473
  if (workflow.maxGenerations !== null && currentGeneration > workflow.maxGenerations) {
1220
1474
  throw new Error(`Workflow generation ${currentGeneration} exceeds Constraint: max_generations = ${workflow.maxGenerations}`);
@@ -1222,10 +1476,8 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1222
1476
 
1223
1477
  this.workflowSteps = workflow.steps;
1224
1478
  this.allowedResolvers = new Set(workflow.allowedResolvers || []);
1225
-
1226
1479
  const mathPattern =
1227
1480
  /^(Add|Subtract|Multiply|Divide|Sum|Avg|Min|Max|Round|Floor|Ceil|Abs)\b/i;
1228
-
1229
1481
  for (const step of workflow.steps) {
1230
1482
  if (step.type === 'calculate' || (step.actionRaw && mathPattern.test(step.actionRaw))) {
1231
1483
  this.allowedResolvers.add('builtInMathResolver');
@@ -1235,9 +1487,23 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1235
1487
  for (const step of workflow.steps) {
1236
1488
  await this.executeStep(step, agentResolver);
1237
1489
  }
1490
+
1491
+ // ✅ AUDIT LOG: Workflow completion (ENHANCED)
1492
+ this._createAuditEntry('workflow_completed', {
1493
+ workflow_id: `${workflow.name}@${workflow.version || 'unversioned'}`,
1494
+ workflow_name: workflow.name,
1495
+ kernel_version: KERNEL_VERSION,
1496
+ runtime_version: KERNEL_VERSION,
1497
+ governance_profile_hash: governanceHash,
1498
+ return_values: workflow.returnValues,
1499
+ total_steps: workflow.steps.length,
1500
+ execution_summary: {
1501
+ warnings: this.__warnings.length,
1502
+ disallowed_attempts: this.disallowedAttempts.length
1503
+ }
1504
+ });
1238
1505
 
1239
1506
  this.printDisallowedSummary();
1240
-
1241
1507
  if (this.__warnings.length) {
1242
1508
  console.log(`\n[O-Lang] ⚠️ Parser/Runtime Warnings (${this.__warnings.length}):`);
1243
1509
  this.__warnings.slice(0, 5).forEach((w, i) => {