@o-lang/olang 1.2.13 → 1.2.15

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,200 @@ 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
- // -----------------------------
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
- // ────────────────────────────────────────────────
724
+ // -----------------------------
725
+ // ✅ KERNEL-LEVEL LLM HALLUCINATION PREVENTION (CONJUGATION-AWARE + EVASION-RESISTANT)
726
+ // -----------------------------
727
+ _validateLLMOutput(output, actionContext) {
728
+ if (!output || typeof output !== 'string') return { passed: true };
466
729
 
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
- );
730
+ // 🔑 Extract allowed capabilities from workflow allowlist
731
+ const allowedCapabilities = Array.from(this.allowedResolvers)
732
+ .filter(name => !name.startsWith('llm-') && name !== 'builtInMathResolver')
733
+ .map(name => name.replace('@o-lang/', '').replace(/-resolver$/, ''));
734
+
735
+ // 🔒 CONJUGATION-AWARE + EVASION-RESISTANT PAN-AFRICAN INTENT DETECTION
736
+ const forbiddenPatterns = [
737
+ // ────────────────────────────────────────────────
738
+ // 🇳🇬 NIGERIAN LANGUAGES (Conjugation-aware)
739
+ // ────────────────────────────────────────────────
740
+ // Yoruba (yo) - Perfective "ti" + Progressive "ń/ǹ/n"
741
+ { pattern: /\bti\s+(?:fi|san|gba|da|lo)\b/i, capability: 'unauthorized_action', lang: 'yo' },
742
+ { pattern: /\b(?:ń|ǹ|n)\s+(?:fi|san|gba)\b/i, capability: 'unauthorized_action', lang: 'yo' },
743
+ { pattern: /\b(fi\s+(?:owo|ẹ̀wọ̀|ewo|ku|fun|s'ọkọọ))\b/i, capability: 'transfer', lang: 'yo' },
744
+ { pattern: /\b(san\s+(?:owo|ẹ̀wọ̀|ewo|fun|wo))\b/i, capability: 'payment', lang: 'yo' },
745
+ { pattern: /\b(gba\s+owo)\b/i, capability: 'withdrawal', lang: 'yo' },
746
+ { pattern: /\b(mo\s+ti\s+(?:fi|san|gba))\b/i, capability: 'unauthorized_action', lang: 'yo' },
482
747
 
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
- };
491
- }
492
- }
493
- }
494
-
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())) {
748
+ // Hausa (ha) - Perfective "ya/ta/su" + Future "za a/za ta"
749
+ { pattern: /\b(?:ya|ta|su)\s+(?:ciyar|biya|sahawa|sake)\b/i, capability: 'unauthorized_action', lang: 'ha' },
750
+ { pattern: /\b(?:za\sa|za\s+ta)\s+(?:ciyar|biya)\b/i, capability: 'unauthorized_action', lang: 'ha' },
751
+ { pattern: /\b(ciyar\s*(?:da)?|ciya\s*(?:da)?|shiga\s+kuɗi)\b/i, capability: 'transfer', lang: 'ha' },
752
+ { pattern: /\b(biya\s*(?:da)?)\b/i, capability: 'payment', lang: 'ha' },
753
+ { pattern: /\b(sahaw[ae]\s+kuɗi)\b/i, capability: 'withdrawal', lang: 'ha' },
754
+ { pattern: /\b(ina\s+(?:ciyar|biya|sahawa))\b/i, capability: 'unauthorized_action', lang: 'ha' },
755
+
756
+ // Igbo (ig) - Perfective suffixes
757
+ { pattern: /\b(?:ziri|bururu|tinyere|gbara)\b/i, capability: 'unauthorized_action', lang: 'ig' },
758
+ { pattern: /\b(zipu\s+(?:ego|moni|isi|na))\b/i, capability: 'transfer', lang: 'ig' },
759
+ { pattern: /\b(buru\s+(?:ego|moni|isi))\b/i, capability: 'transfer', lang: 'ig' },
760
+ { pattern: /\b(tinye\s+(?:ego|moni|isi))\b/i, capability: 'deposit', lang: 'ig' },
761
+ { pattern: /\b(m\s+(?:ziri|buru|zipuru|tinyere))\b/i, capability: 'unauthorized_action', lang: 'ig' },
762
+
763
+ // ────────────────────────────────────────────────
764
+ // 🌍 PAN-AFRICAN LANGUAGES (Conjugation-aware + Evasion-resistant)
765
+ // ────────────────────────────────────────────────
766
+ // Swahili (sw) - ALL ASPECTS: Perfect, Continuous Passive, Future
767
+ { 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' },
768
+ { 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' },
769
+ { 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' },
770
+ { 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' },
771
+ { pattern: /\b(tuma\s+(?:pesa|fedha)|pelek[ae]?\s+(?:pesa|fedha)|wasilisha)\b/i, capability: 'transfer', lang: 'sw' },
772
+ { pattern: /\b(lipa|maliza\s+malipo)\b/i, capability: 'payment', lang: 'sw' },
773
+ { pattern: /\b(ongez[ae]?\s*(?:kiasi|pesa|fedha)|wek[ae]?\s+(?:katika|ndani)\s+(?:akaunti|hisa))\b/i, capability: 'deposit', lang: 'sw' },
774
+ { pattern: /\b(nime(?:tuma|lipa|ongeza|weka|peleka))\b/i, capability: 'unauthorized_action', lang: 'sw' },
775
+
776
+ // Amharic (am) - Perfective suffix (Ethiopic script)
777
+ { pattern: /[\u1200-\u137F]{0,4}(?:ተላላፈ|ላክ|ክፈል|ጨምር|ወጣ|ገባ)[\u1200-\u137F]{0,2}(?:\u1205|\u122d|\u1265)[\u1200-\u137F]{0,2}/u, capability: 'financial_action', lang: 'am' },
778
+
779
+ // Oromo (om) - Perfective "ni...e"
780
+ { pattern: /\bni\s+(?:kuufe|dhiibe|kennine|gurgure)\b/i, capability: 'unauthorized_action', lang: 'om' },
781
+ { pattern: /\b(kuuf\s+(?:qilleensaa|bilbila)|dhiib\s+(?:qilleensaa|bilbila))\b/i, capability: 'transfer', lang: 'om' },
782
+ { pattern: /\b(kenn\s*i|gurgur\s*i)\b/i, capability: 'payment', lang: 'om' },
783
+
784
+ // Fula (ff)
785
+ { pattern: /\b(sakkit\s+(?:ndo|ndoo)|tawt\s+(?:ndo|ndoo))\b/i, capability: 'transfer', lang: 'ff' },
786
+ { pattern: /\b(jokk\s*i|soodug\s*i)\b/i, capability: 'payment', lang: 'ff' },
787
+
788
+ // Somali (so) - Perfective "waxaa"
789
+ { pattern: /\bwaxaa\s+(?:diray|bixiyay|ku\s+daray|sameeyay)\b/i, capability: 'unauthorized_action', lang: 'so' },
790
+ { pattern: /\b(dir\s+(?:lacag|maal|qarsoon))\b/i, capability: 'transfer', lang: 'so' },
791
+ { pattern: /\b(bixi|bixis\s*o)\b/i, capability: 'payment', lang: 'so' },
792
+
793
+ // Zulu (zu) - Perfective "-ile"
794
+ { pattern: /\b(?:thumel|hlawul|fik)\s*ile\b/i, capability: 'unauthorized_action', lang: 'zu' },
795
+ { pattern: /\b(thumel\s*a\s+(?:imali|imali))\b/i, capability: 'transfer', lang: 'zu' },
796
+ { pattern: /\b(hlawul\s*a|hlawulel\s*a)\b/i, capability: 'payment', lang: 'zu' },
797
+ { pattern: /\b(siyithumel\s*e|siyihlawul\s*e)\b/i, capability: 'unauthorized_action', lang: 'zu' },
798
+
799
+ // Shona (sn) - Perfective "-a/-e"
800
+ { pattern: /\b(?:tumir|bhadhar)\s*a\b/i, capability: 'unauthorized_action', lang: 'sn' },
801
+ { pattern: /\b(tumir\s*a\s+(?:mhando|ari))\b/i, capability: 'transfer', lang: 'sn' },
802
+ { pattern: /\b(bhadhara|bhadharis\s*o)\b/i, capability: 'payment', lang: 'sn' },
803
+
804
+ // ────────────────────────────────────────────────
805
+ // 🌐 GLOBAL LANGUAGES (Conjugation-aware)
806
+ // ────────────────────────────────────────────────
807
+ // English (en) - Perfective + Passive
808
+ { pattern: /\b(?:have|has|had)\s+(?:transferred|sent|paid|withdrawn|deposited|wire[d])\b/i, capability: 'unauthorized_action', lang: 'en' },
809
+ { pattern: /\b(?:was|were|been)\s+(?:added|credited|transferred|sent|paid)\b/i, capability: 'unauthorized_action', lang: 'en' },
810
+ { 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' },
811
+ { 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' },
812
+
813
+ // French (fr) - Past participle
814
+ { 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' },
815
+ { pattern: /\b(virer|transférer|envoyer|payer|retirer|déposer|débiter|créditer)\b/i, capability: 'financial_action', lang: 'fr' },
816
+
817
+ // Arabic (ar) - Perfective past tense
818
+ { pattern: /[\u0600-\u06FF]{0,3}(?:حوّل|أرسل|ادفع|اودع|سحب)[\u0600-\u06FF]{0,3}(?:ت|نا|تم|تا|تِ|تُ|تَ)[\u0600-\u06FF]{0,3}/u, capability: 'financial_action', lang: 'ar' },
819
+ { pattern: /[\u0600-\u06FF]{0,3}(?:أنا|تم|لقد)\s*(?:حوّلت|أرسلت|دفعت|اودعت)[\u0600-\u06FF]{0,3}/u, capability: 'unauthorized_action', lang: 'ar' },
820
+
821
+ // Chinese (zh) - Perfective "le" particle
822
+ { pattern: /[\u4e00-\u9fff]{0,2}(?:转账 | 支付 | 存款 | 取款)[\u4e00-\u9fff]{0,2}(?:了)[\u4e00-\u9fff]{0,2}/u, capability: 'financial_action', lang: 'zh' },
823
+ { pattern: /[\u4e00-\u9fff]{0,2}(?:转账 | 转帐 | 支付 | 付款 | 提款 | 取款 | 存款 | 存入 | 汇款 | 存)[\u4e00-\u9fff]{0,2}/u, capability: 'financial_action', lang: 'zh' },
824
+ { pattern: /[\u4e00-\u9fff]{0,2}(?:我 | 已 | 已经)\s*(?:转账 | 支付 | 提款 | 存款)[\u4e00-\u9fff]{0,2}/u, capability: 'unauthorized_action', lang: 'zh' },
825
+
826
+ // ────────────────────────────────────────────────
827
+ // 🛡️ EVASION-RESISTANT NUMERIC DECEPTION
828
+ // ────────────────────────────────────────────────
829
+ {
830
+ 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,
831
+ capability: 'unauthorized_action',
832
+ lang: 'multi'
833
+ },
834
+
835
+ // ────────────────────────────────────────────────
836
+ // 🔒 PII LEAKAGE PATTERNS
837
+ // ────────────────────────────────────────────────
838
+ { 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' },
839
+ { pattern: /\b(?:bvn|bank verification number)\s*[:\-]?\s*(\d{11})\b/i, capability: 'pii_exposure', lang: 'multi' },
840
+ { pattern: /\b(?:\+?234\s*|0)(?:70|80|81|90|91)\d{8}\b/, capability: 'pii_exposure', lang: 'multi' },
841
+
842
+ // ────────────────────────────────────────────────
843
+ // ✅ FAKE CONFIRMATION PATTERNS
844
+ // ────────────────────────────────────────────────
845
+ { 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' }
846
+ ];
847
+
848
+ // 🔍 SCAN OUTPUT FOR FORBIDDEN INTENTS
849
+ for (const { pattern, capability, lang } of forbiddenPatterns) {
850
+ if (pattern.test(output)) {
851
+ const hasCapability = allowedCapabilities.some(c =>
852
+ c.includes(capability) ||
853
+ c.includes('transfer') ||
854
+ c.includes('payment') ||
855
+ c.includes('financial') ||
856
+ c.includes('deposit') ||
857
+ c.includes('withdraw')
858
+ );
859
+
860
+ if (!hasCapability) {
861
+ const match = output.match(pattern);
503
862
  return {
504
863
  passed: false,
505
- reason: `Resolver output violates prohibited topic "${topic}" defined in __verified_intent`,
506
- detected: topic,
507
- language: 'multi'
864
+ reason: `Hallucinated "${capability}" capability in ${lang} (not in workflow allowlist: ${allowedCapabilities.join(', ') || 'none'})`,
865
+ detected: match ? match[0].trim() : 'unknown pattern',
866
+ language: lang
508
867
  };
509
868
  }
510
869
  }
511
870
  }
512
-
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())) {
518
- return {
519
- passed: false,
520
- reason: `Resolver output violates prohibited action "${action}" defined in __verified_intent`,
521
- detected: action,
522
- language: 'multi'
523
- };
871
+
872
+ // SEMANTIC INTENT DRIFT DETECTION
873
+ const intent = this.context.__verified_intent;
874
+ if (intent) {
875
+ if (intent.prohibited_topics && Array.isArray(intent.prohibited_topics)) {
876
+ const lower = output.toLowerCase();
877
+ for (const topic of intent.prohibited_topics) {
878
+ if (lower.includes(topic.toLowerCase())) {
879
+ return {
880
+ passed: false,
881
+ reason: `Resolver output violates prohibited topic "${topic}" defined in __verified_intent`,
882
+ detected: topic,
883
+ language: 'multi'
884
+ };
885
+ }
886
+ }
887
+ }
888
+
889
+ if (intent.prohibited_actions && Array.isArray(intent.prohibited_actions)) {
890
+ const lower = output.toLowerCase();
891
+ for (const action of intent.prohibited_actions) {
892
+ if (lower.includes(action.toLowerCase())) {
893
+ return {
894
+ passed: false,
895
+ reason: `Resolver output violates prohibited action "${action}" defined in __verified_intent`,
896
+ detected: action,
897
+ language: 'multi'
898
+ };
899
+ }
524
900
  }
525
901
  }
526
902
  }
903
+
904
+ return { passed: true };
527
905
  }
528
906
 
529
- return { passed: true };
530
- }
531
907
  // -----------------------------
532
908
  // ✅ CRITICAL FIX: Resolver output unwrapping helper
533
909
  // -----------------------------
534
910
  _unwrapResolverResult(result) {
535
- // Standard O-Lang resolver contract: { output: {...} } or { error: "..." }
536
911
  if (result && typeof result === 'object' && 'output' in result && result.output !== undefined) {
537
912
  return result.output;
538
913
  }
539
- // Legacy resolvers might return raw values
540
914
  return result;
541
915
  }
542
916
 
@@ -545,11 +919,10 @@ _validateLLMOutput(output, actionContext) {
545
919
  // -----------------------------
546
920
  async executeStep(step, agentResolver) {
547
921
  const stepType = step.type;
548
-
922
+
549
923
  // ✅ Enforce per-step constraints (basic validation)
550
924
  if (step.constraints && Object.keys(step.constraints).length > 0) {
551
925
  for (const [key, value] of Object.entries(step.constraints)) {
552
- // Log unsupported constraints (future extensibility)
553
926
  if (['max_time_sec', 'cost_limit', 'allowed_resolvers'].includes(key)) {
554
927
  this.addWarning(`Per-step constraint "${key}=${value}" is parsed but not yet enforced`);
555
928
  } else {
@@ -561,19 +934,15 @@ _validateLLMOutput(output, actionContext) {
561
934
  // ✅ ADDITION 3 — Resolver Policy Enforcement (External + Local)
562
935
  const enforceResolverPolicy = (resolver, step) => {
563
936
  const resolverName = resolver?.resolverName || resolver?.name;
564
-
565
937
  if (!resolverName) {
566
938
  throw new Error('[O-Lang] Resolver missing resolverName');
567
939
  }
568
-
569
940
  if (!this.allowedResolvers.has(resolverName)) {
570
941
  this.logDisallowedResolver(resolverName, step.actionRaw || step.type);
571
942
  throw new Error(
572
943
  `[O-Lang] Resolver "${resolverName}" blocked by workflow policy`
573
944
  );
574
945
  }
575
-
576
- // External resolvers MUST be HTTP-only
577
946
  if (this._isExternalResolver(resolver)) {
578
947
  if (!resolver.manifest.endpoint) {
579
948
  throw new Error(
@@ -583,11 +952,10 @@ _validateLLMOutput(output, actionContext) {
583
952
  }
584
953
  };
585
954
 
586
- // ✅ CORRECTED: Strict safety WITH dynamic diagnostics (FIXED SCOPE ERROR)
955
+ // ✅ CORRECTED: Strict safety WITH dynamic diagnostics
587
956
  const runResolvers = async (action) => {
588
957
  const mathPattern =
589
958
  /^(Add|Subtract|Multiply|Divide|Sum|Avg|Min|Max|Round|Floor|Ceil|Abs)\b/i;
590
-
591
959
  if (
592
960
  step.actionRaw &&
593
961
  mathPattern.test(step.actionRaw) &&
@@ -596,9 +964,7 @@ _validateLLMOutput(output, actionContext) {
596
964
  this.allowedResolvers.add('builtInMathResolver');
597
965
  }
598
966
 
599
- // Handle different resolver input formats
600
967
  let resolversToRun = [];
601
-
602
968
  if (agentResolver && Array.isArray(agentResolver._chain)) {
603
969
  resolversToRun = agentResolver._chain;
604
970
  } else if (Array.isArray(agentResolver)) {
@@ -607,9 +973,7 @@ _validateLLMOutput(output, actionContext) {
607
973
  resolversToRun = [agentResolver];
608
974
  }
609
975
 
610
- // ✅ Track detailed resolver outcomes for diagnostics
611
976
  const resolverAttempts = [];
612
-
613
977
  for (let idx = 0; idx < resolversToRun.length; idx++) {
614
978
  const resolver = resolversToRun[idx];
615
979
  const resolverName = resolver?.resolverName || resolver?.name || `resolver-${idx}`;
@@ -617,7 +981,6 @@ _validateLLMOutput(output, actionContext) {
617
981
 
618
982
  try {
619
983
  let result;
620
-
621
984
  if (this._isExternalResolver(resolver)) {
622
985
  result = await this._callExternalResolver(
623
986
  resolver,
@@ -628,47 +991,35 @@ _validateLLMOutput(output, actionContext) {
628
991
  result = await resolver(action, this.context);
629
992
  }
630
993
 
631
- // ✅ ACCEPT valid result immediately (non-null/non-undefined)
632
994
  if (result !== undefined && result !== null) {
633
- // ✅ CRITICAL FIX: Save raw result for debugging (like __resolver_0)
634
995
  this.context[`__resolver_${idx}`] = result;
635
-
636
- // ✅ UNWRAP before returning to workflow logic
637
996
  return this._unwrapResolverResult(result);
638
997
  }
639
-
640
- // ⚪ Resolver skipped this action (normal behavior)
998
+
641
999
  resolverAttempts.push({
642
1000
  name: resolverName,
643
1001
  status: 'skipped',
644
1002
  reason: 'Action not recognized'
645
1003
  });
646
-
647
1004
  } catch (e) {
648
- // ❌ Resolver attempted but failed — capture structured diagnostics
649
1005
  const diagnostics = {
650
1006
  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
1007
+ requiredEnvVars: e.requiredEnvVars || [],
1008
+ missingInputs: e.missingInputs || [],
1009
+ documentationUrl: resolver?.documentationUrl ||
1010
+ (resolver?.manifest?.documentationUrl) || null
655
1011
  };
656
-
657
1012
  resolverAttempts.push({
658
1013
  name: resolverName,
659
1014
  status: 'failed',
660
1015
  diagnostics
661
1016
  });
662
-
663
- // Log for verbose mode but continue chaining
664
1017
  this.addWarning(`Resolver "${resolverName}" failed for action "${action}": ${diagnostics.error}`);
665
1018
  }
666
1019
  }
667
1020
 
668
- // BUILD DYNAMIC, ACTIONABLE ERROR MESSAGE (FIXED: NO SCOPE ERRORS)
669
- let errorMessage = `[O-Lang SAFETY] No resolver handled action: "${action}"\n\n`;
1021
+ let errorMessage = `[O-Lang SAFETY] No resolver handled action: "${action}\n`;
670
1022
  errorMessage += `Attempted resolvers:\n`;
671
-
672
1023
  resolverAttempts.forEach((attempt, i) => {
673
1024
  const namePad = attempt.name.padEnd(30);
674
1025
  if (attempt.status === 'skipped') {
@@ -676,25 +1027,18 @@ _validateLLMOutput(output, actionContext) {
676
1027
  } else {
677
1028
  errorMessage += ` ${i + 1}. ${namePad} → FAILED\n`;
678
1029
  errorMessage += ` Error: ${attempt.diagnostics.error}\n`;
679
-
680
- // ✅ DYNAMIC HINT: Resolver-provided env vars
681
1030
  if (attempt.diagnostics.requiredEnvVars?.length) {
682
1031
  errorMessage += ` Required env vars: ${attempt.diagnostics.requiredEnvVars.join(', ')}\n`;
683
1032
  }
684
-
685
- // ✅ DYNAMIC HINT: Resolver-provided docs link
686
1033
  if (attempt.diagnostics.documentationUrl) {
687
1034
  errorMessage += ` Docs: ${attempt.diagnostics.documentationUrl}\n`;
688
1035
  }
689
1036
  }
690
1037
  });
691
1038
 
692
- // ✅ ACCURATE REMEDIATION (NO OBSOLETE "REMOVE ACTION KEYWORD" HINT)
693
1039
  const failed = resolverAttempts.filter(a => a.status === 'failed');
694
1040
  const allSkipped = failed.length === 0;
695
-
696
1041
  errorMessage += `\n💡 How to fix:\n`;
697
-
698
1042
  if (allSkipped) {
699
1043
  errorMessage += ` • Verify the action matches a resolver's capabilities:\n`;
700
1044
  errorMessage += ` → Check resolver documentation for supported actions\n`;
@@ -705,27 +1049,22 @@ _validateLLMOutput(output, actionContext) {
705
1049
  errorMessage += ` → Set required environment variables (if listed)\n`;
706
1050
  errorMessage += ` → Verify inputs exist in workflow context\n`;
707
1051
  errorMessage += ` → Check resolver documentation for requirements\n`;
708
-
709
- // Pattern-based hints (generic, not hardcoded)
710
1052
  const envVarPattern = /environment variable|env\.|process\.env|missing.*path/i;
711
1053
  if (failed.some(f => envVarPattern.test(f.diagnostics.error))) {
712
1054
  errorMessage += ` → Example (PowerShell): $env:VARIABLE="value"\n`;
713
1055
  errorMessage += ` → Example (Linux/macOS): export VARIABLE="value"\n`;
714
1056
  }
715
-
716
1057
  const dbPattern = /database|db\.|sqlite|postgres|mysql|mongodb/i;
717
1058
  if (failed.some(f => dbPattern.test(f.diagnostics.error))) {
718
1059
  errorMessage += ` → Ensure database file/connection exists and path is correct\n`;
719
1060
  }
720
-
721
1061
  const authPattern = /auth|api key|token|credential/i;
722
1062
  if (failed.some(f => authPattern.test(f.diagnostics.error))) {
723
1063
  errorMessage += ` → Verify API keys/tokens are set in environment variables\n`;
724
1064
  }
725
1065
  }
726
-
727
- // FIXED: NO SCOPE ERROR IN FALLBACK DOCUMENTATION URL
728
- errorMessage += `\n • Resolver documentation:\n`;
1066
+
1067
+ errorMessage += `\n• Resolver documentation:\n`;
729
1068
  let hasDocs = false;
730
1069
  resolverAttempts.forEach(attempt => {
731
1070
  if (attempt.diagnostics?.documentationUrl) {
@@ -734,9 +1073,8 @@ _validateLLMOutput(output, actionContext) {
734
1073
  }
735
1074
  });
736
1075
  if (!hasDocs) {
737
- errorMessage += ` → Visit https://www.npmjs.com/search?q=%40o-lang for resolver packages\n`; // ✅ FIXED
1076
+ errorMessage += ` → Visit https://www.npmjs.com/search?q=%40o-lang for resolver packages\n`;
738
1077
  }
739
-
740
1078
  errorMessage += `\n🛑 Workflow halted to prevent unsafe data propagation to LLMs.`;
741
1079
  throw new Error(errorMessage);
742
1080
  };
@@ -747,161 +1085,143 @@ _validateLLMOutput(output, actionContext) {
747
1085
  if (step.saveAs) this.context[step.saveAs] = result;
748
1086
  break;
749
1087
  }
1088
+
1089
+ case 'action': {
1090
+ let action = this._safeInterpolate(
1091
+ step.actionRaw,
1092
+ this.context,
1093
+ 'action step'
1094
+ );
750
1095
 
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
- }
1096
+ if (action.startsWith('Ask ')) {
1097
+ action = 'Action ' + action.slice(4);
1098
+ } else if (action.startsWith('Use ')) {
1099
+ action = 'Action ' + action.slice(4);
1100
+ }
774
1101
 
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
- });
1102
+ if (!action.startsWith('Action ')) {
1103
+ throw new Error(
1104
+ `[O-Lang SAFETY] Non-canonical action received: "${action}\n` +
1105
+ ` → Expected format: Action <resolver> <args>\n` +
1106
+ ` → This indicates a kernel or workflow authoring error.`
1107
+ );
1108
+ }
787
1109
 
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
- }
1110
+ const mathCall = action.match(
1111
+ /^(add|subtract|multiply|divide|sum|avg|min|max|round|floor|ceil|abs)\((.*)\)$/i
1112
+ );
1113
+ if (mathCall) {
1114
+ const fn = mathCall[1].toLowerCase();
1115
+ const args = mathCall[2].split(',').map(s => {
1116
+ s = s.trim();
1117
+ if (!isNaN(s)) return parseFloat(s);
1118
+ return this.getNested(this.context, s.replace(/^\{|\}$/g, ''));
1119
+ });
1120
+ if (this.mathFunctions[fn]) {
1121
+ const value = this.mathFunctions[fn](...args);
1122
+ if (step.saveAs) this.context[step.saveAs] = value;
1123
+ break;
1124
+ }
1125
+ }
794
1126
 
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
- }
1127
+ const rawResult = await runResolvers(action);
1128
+ const unwrapped = this._unwrapResolverResult(rawResult);
852
1129
 
853
- if (step.saveAs) {
854
- this.context[step.saveAs] = unwrapped;
855
- }
856
- break;
857
- }
1130
+ const isLLMAction = action.toLowerCase().includes('groq') ||
1131
+ action.toLowerCase().includes('openai') ||
1132
+ action.toLowerCase().includes('anthropic') ||
1133
+ action.toLowerCase().includes('claude') ||
1134
+ action.toLowerCase().includes('gpt') ||
1135
+ action.toLowerCase().includes('gemini') ||
1136
+ action.toLowerCase().includes('google') ||
1137
+ action.toLowerCase().includes('llama') ||
1138
+ action.toLowerCase().includes('meta') ||
1139
+ action.toLowerCase().includes('mistral') ||
1140
+ action.toLowerCase().includes('mixtral') ||
1141
+ action.toLowerCase().includes('cohere') ||
1142
+ action.toLowerCase().includes('huggingface') ||
1143
+ action.toLowerCase().includes('hugging-face') ||
1144
+ action.toLowerCase().includes('together') ||
1145
+ action.toLowerCase().includes('perplexity') ||
1146
+ action.toLowerCase().includes('fireworks') ||
1147
+ action.toLowerCase().includes('bedrock') ||
1148
+ action.toLowerCase().includes('azure') ||
1149
+ action.toLowerCase().includes('ollama') ||
1150
+ action.toLowerCase().includes('replicate') ||
1151
+ action.toLowerCase().includes('deepseek') ||
1152
+ action.toLowerCase().includes('qwen') ||
1153
+ action.toLowerCase().includes('falcon') ||
1154
+ action.toLowerCase().includes('phi') ||
1155
+ action.toLowerCase().includes('gemma') ||
1156
+ action.toLowerCase().includes('stablelm') ||
1157
+ action.toLowerCase().includes('yi') ||
1158
+ action.toLowerCase().includes('dbrx') ||
1159
+ action.toLowerCase().includes('command') ||
1160
+ action.toLowerCase().includes('llm');
1161
+
1162
+ const llmText = unwrapped?.response ||
1163
+ unwrapped?.text ||
1164
+ unwrapped?.content ||
1165
+ unwrapped?.answer ||
1166
+ (typeof unwrapped === 'string' ? unwrapped : null);
1167
+
1168
+ if (isLLMAction && typeof llmText === 'string') {
1169
+ const safetyCheck = this._validateLLMOutput(llmText, action);
1170
+ if (!safetyCheck.passed) {
1171
+ throw new Error(
1172
+ `[O-Lang SAFETY] LLM hallucinated unauthorized capability:\n` +
1173
+ ` → Detected: "${safetyCheck.detected}"\n` +
1174
+ ` → Reason: ${safetyCheck.reason}\n` +
1175
+ ` → Workflow allowlist: ${Array.from(this.allowedResolvers).join(', ')}\n` +
1176
+ `\n🛑 Halting to prevent deceptive user experience.`
1177
+ );
1178
+ }
1179
+ }
858
1180
 
1181
+ if (step.saveAs) {
1182
+ this.context[step.saveAs] = unwrapped;
1183
+ }
1184
+ break;
1185
+ }
1186
+
859
1187
  case 'use': {
860
- // ✅ SAFE INTERPOLATION for tool name
861
1188
  const tool = this._safeInterpolate(step.tool, this.context, 'tool name');
862
1189
  const rawResult = await runResolvers(`Use ${tool}`);
863
1190
  const unwrapped = this._unwrapResolverResult(rawResult);
864
-
865
1191
  if (step.saveAs) this.context[step.saveAs] = unwrapped;
866
1192
  break;
867
1193
  }
1194
+
1195
+ case 'ask': {
1196
+ const target = this._safeInterpolate(step.target, this.context, 'LLM prompt');
1197
+ if (/{[^}]+}/.test(target)) {
1198
+ throw new Error(`[O-Lang] Unresolved variables in prompt: "${target}"`);
1199
+ }
1200
+ const rawResult = await runResolvers(`Action ${target}`);
1201
+ const unwrapped = this._unwrapResolverResult(rawResult);
868
1202
 
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
- }
1203
+ if (typeof unwrapped?.output === 'string') {
1204
+ const safetyCheck = this._validateLLMOutput(unwrapped.output, target);
1205
+ if (!safetyCheck.passed) {
1206
+ throw new Error(
1207
+ `[O-Lang SAFETY] LLM hallucinated unauthorized capability:\n` +
1208
+ ` → Detected: "${safetyCheck.detected}"\n` +
1209
+ ` → Reason: ${safetyCheck.reason}\n` +
1210
+ `Workflow allowlist: ${Array.from(this.allowedResolvers).join(', ')}\n` +
1211
+ `\n🛑 Halting to prevent deceptive user experience.`
1212
+ );
1213
+ }
1214
+ }
897
1215
 
1216
+ if (step.saveAs) this.context[step.saveAs] = unwrapped;
1217
+ break;
1218
+ }
1219
+
898
1220
  case 'evolve': {
899
1221
  const { targetResolver, feedback } = step;
900
-
901
1222
  if (this.verbose) {
902
1223
  console.log(`🔄 Evolve step: ${targetResolver} with feedback: "${feedback}"`);
903
1224
  }
904
-
905
1225
  const evolutionResult = {
906
1226
  resolver: targetResolver,
907
1227
  feedback: feedback,
@@ -909,41 +1229,34 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
909
1229
  timestamp: new Date().toISOString(),
910
1230
  workflow: this.context.workflow_name
911
1231
  };
912
-
913
1232
  if (process.env.OLANG_EVOLUTION_API_KEY) {
914
1233
  evolutionResult.status = 'advanced_evolution_enabled';
915
1234
  evolutionResult.message = 'Advanced evolution service would process this request';
916
1235
  }
917
-
918
1236
  if (step.saveAs) {
919
1237
  this.context[step.saveAs] = evolutionResult;
920
1238
  }
921
1239
  break;
922
1240
  }
923
-
1241
+
924
1242
  case 'if': {
925
1243
  if (this.evaluateCondition(step.condition, this.context)) {
926
1244
  for (const s of step.body) await this.executeStep(s, agentResolver);
927
1245
  }
928
1246
  break;
929
1247
  }
930
-
1248
+
931
1249
  case 'parallel': {
932
1250
  const { steps, timeout } = step;
933
-
934
1251
  if (timeout !== undefined && timeout > 0) {
935
- // Timed parallel execution
936
1252
  const timeoutPromise = new Promise(resolve => {
937
1253
  setTimeout(() => resolve({ timedOut: true }), timeout);
938
1254
  });
939
-
940
1255
  const parallelPromise = Promise.all(
941
1256
  steps.map(s => this.executeStep(s, agentResolver))
942
1257
  ).then(() => ({ timedOut: false }));
943
-
944
1258
  const result = await Promise.race([timeoutPromise, parallelPromise]);
945
1259
  this.context.timed_out = result.timedOut;
946
-
947
1260
  if (result.timedOut) {
948
1261
  this.emit('parallel_timeout', { duration: timeout, steps: steps.length });
949
1262
  if (this.verbose) {
@@ -951,28 +1264,23 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
951
1264
  }
952
1265
  }
953
1266
  } else {
954
- // Normal parallel execution (no timeout)
955
1267
  await Promise.all(steps.map(s => this.executeStep(s, agentResolver)));
956
1268
  this.context.timed_out = false;
957
1269
  }
958
1270
  break;
959
1271
  }
960
-
1272
+
961
1273
  case 'escalation': {
962
1274
  const { levels } = step;
963
1275
  let finalResult = null;
964
1276
  let currentTimeout = 0;
965
1277
  let completedLevel = null;
966
-
967
1278
  for (const level of levels) {
968
1279
  if (level.timeout === 0) {
969
- // Immediate execution (no timeout)
970
1280
  const levelSteps = require('./parser').parseBlock(level.steps);
971
1281
  for (const levelStep of levelSteps) {
972
1282
  await this.executeStep(levelStep, agentResolver);
973
1283
  }
974
-
975
- // Check if the target variable was set in this level
976
1284
  if (levelSteps.length > 0) {
977
1285
  const lastStep = levelSteps[levelSteps.length - 1];
978
1286
  if (lastStep.saveAs && this.context[lastStep.saveAs] !== undefined) {
@@ -982,13 +1290,10 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
982
1290
  }
983
1291
  }
984
1292
  } else {
985
- // Timed execution for this level
986
1293
  currentTimeout += level.timeout;
987
-
988
1294
  const timeoutPromise = new Promise(resolve => {
989
1295
  setTimeout(() => resolve({ timedOut: true }), level.timeout);
990
1296
  });
991
-
992
1297
  const levelPromise = (async () => {
993
1298
  const levelSteps = require('./parser').parseBlock(level.steps);
994
1299
  for (const levelStep of levelSteps) {
@@ -996,11 +1301,8 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
996
1301
  }
997
1302
  return { timedOut: false };
998
1303
  })();
999
-
1000
1304
  const result = await Promise.race([timeoutPromise, levelPromise]);
1001
-
1002
1305
  if (!result.timedOut) {
1003
- // Level completed successfully
1004
1306
  if (levelSteps && levelSteps.length > 0) {
1005
1307
  const lastStep = levelSteps[levelSteps.length - 1];
1006
1308
  if (lastStep.saveAs && this.context[lastStep.saveAs] !== undefined) {
@@ -1010,32 +1312,27 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1010
1312
  }
1011
1313
  }
1012
1314
  }
1013
- // If timed out, continue to next level
1014
1315
  }
1015
1316
  }
1016
-
1017
- // Set escalation status in context
1018
1317
  this.context.escalation_completed = finalResult !== null;
1019
1318
  this.context.timed_out = finalResult === null;
1020
1319
  if (completedLevel !== null) {
1021
1320
  this.context.escalation_level = completedLevel;
1022
1321
  }
1023
-
1024
1322
  break;
1025
1323
  }
1026
-
1324
+
1027
1325
  case 'connect': {
1028
1326
  this.resources[step.resource] = step.endpoint;
1029
1327
  break;
1030
1328
  }
1031
-
1329
+
1032
1330
  case 'agent_use': {
1033
1331
  this.agentMap[step.logicalName] = step.resource;
1034
1332
  break;
1035
1333
  }
1036
-
1334
+
1037
1335
  case 'debrief': {
1038
- // ✅ SEMANTIC VALIDATION: Check symbols in message
1039
1336
  if (step.message.includes('{')) {
1040
1337
  const symbols = step.message.match(/\{([^\}]+)\}/g) || [];
1041
1338
  for (const symbolMatch of symbols) {
@@ -1046,22 +1343,17 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1046
1343
  this.emit('debrief', { agent: step.agent, message: step.message });
1047
1344
  break;
1048
1345
  }
1049
-
1050
- // ✅ NEW: Prompt step handler
1346
+
1051
1347
  case 'prompt': {
1052
1348
  if (this.verbose) {
1053
1349
  console.log(`❓ Prompt: ${step.question}`);
1054
1350
  }
1055
- // In non-interactive mode, leave as no-op
1056
1351
  break;
1057
1352
  }
1058
-
1059
- // ✅ NEW: Emit step handler with semantic validation
1353
+
1060
1354
  case 'emit': {
1061
- // ✅ SEMANTIC VALIDATION: Check all symbols in payload
1062
1355
  const payloadTemplate = step.payload;
1063
1356
  const symbols = [...new Set(payloadTemplate.match(/\{([^\}]+)\}/g) || [])];
1064
-
1065
1357
  let shouldEmit = true;
1066
1358
  for (const symbolMatch of symbols) {
1067
1359
  const symbol = symbolMatch.replace(/[{}]/g, '');
@@ -1069,78 +1361,62 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1069
1361
  shouldEmit = false;
1070
1362
  }
1071
1363
  }
1072
-
1073
1364
  if (!shouldEmit) {
1074
1365
  if (this.verbose) {
1075
1366
  console.log(`⏭️ Skipped emit due to missing semantic symbols`);
1076
1367
  }
1077
1368
  break;
1078
1369
  }
1079
-
1080
- // ✅ SAFE INTERPOLATION for emit payload
1081
1370
  const payload = this._safeInterpolate(step.payload, this.context, 'emit payload');
1082
-
1083
- this.emit(step.event, {
1371
+ this.emit(step.event, {
1084
1372
  payload: payload,
1085
1373
  workflow: this.context.workflow_name,
1086
1374
  timestamp: new Date().toISOString()
1087
1375
  });
1088
-
1089
1376
  if (this.verbose) {
1090
1377
  console.log(`📤 Emit event "${step.event}" with payload: ${payload}`);
1091
1378
  }
1092
1379
  break;
1093
1380
  }
1094
-
1095
- // ✅ File Persist step handler with semantic validation
1381
+
1096
1382
  case 'persist': {
1097
- // ✅ SEMANTIC VALIDATION: Require symbol exists
1098
1383
  if (!this._requireSemantic(step.variable, 'persist')) {
1099
1384
  if (this.verbose) {
1100
1385
  console.log(`⏭️ Skipped persist for undefined "${step.variable}"`);
1101
1386
  }
1102
1387
  break;
1103
1388
  }
1104
-
1105
1389
  const sourceValue = this.context[step.variable];
1106
1390
  const outputPath = path.resolve(process.cwd(), step.target);
1107
1391
  const outputDir = path.dirname(outputPath);
1108
1392
  if (!fs.existsSync(outputDir)) {
1109
1393
  fs.mkdirSync(outputDir, { recursive: true });
1110
1394
  }
1111
-
1112
1395
  let content;
1113
1396
  if (step.target.endsWith('.json')) {
1114
1397
  content = JSON.stringify(sourceValue, null, 2);
1115
1398
  } else {
1116
1399
  content = String(sourceValue);
1117
1400
  }
1118
-
1119
1401
  fs.writeFileSync(outputPath, content, 'utf8');
1120
-
1121
1402
  if (this.verbose) {
1122
1403
  console.log(`💾 Persisted "${step.variable}" to ${step.target}`);
1123
1404
  }
1124
1405
  break;
1125
1406
  }
1126
-
1127
- // ✅ NEW: Database persist handler with semantic validation
1407
+
1128
1408
  case 'persist-db': {
1129
1409
  if (!this.dbClient) {
1130
1410
  this.addWarning(`DB persistence skipped (no DB configured). Set OLANG_DB_TYPE env var.`);
1131
1411
  break;
1132
1412
  }
1133
-
1134
- // ✅ SEMANTIC VALIDATION: Require symbol exists
1135
1413
  if (!this._requireSemantic(step.variable, 'persist-db')) {
1136
1414
  if (this.verbose) {
1137
1415
  console.log(`⏭️ Skipped DB persist for undefined "${step.variable}"`);
1138
1416
  }
1139
1417
  break;
1140
1418
  }
1141
-
1142
1419
  const sourceValue = this.context[step.variable];
1143
-
1144
1420
  try {
1145
1421
  switch (this.dbClient.type) {
1146
1422
  case 'postgres':
@@ -1157,16 +1433,14 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1157
1433
  );
1158
1434
  }
1159
1435
  break;
1160
-
1161
1436
  case 'mongodb':
1162
1437
  const db = this.dbClient.client.db(process.env.DB_NAME || 'olang');
1163
1438
  await db.collection(step.collection).insertOne({
1164
1439
  workflow_name: this.context.workflow_name || 'unknown',
1165
- data: sourceValue, // ✅ FIXED: Added property name "data" (was broken syntax)
1440
+ data: sourceValue,
1166
1441
  created_at: new Date()
1167
1442
  });
1168
1443
  break;
1169
-
1170
1444
  case 'sqlite':
1171
1445
  const stmt = this.dbClient.client.prepare(
1172
1446
  `INSERT INTO ${step.collection} (workflow_name, data, created_at) VALUES (?, ?, ?)`
@@ -1178,7 +1452,6 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1178
1452
  );
1179
1453
  break;
1180
1454
  }
1181
-
1182
1455
  if (this.verbose) {
1183
1456
  console.log(`🗄️ Persisted "${step.variable}" to DB collection ${step.collection}`);
1184
1457
  }
@@ -1199,22 +1472,41 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1199
1472
  if (workflow.type !== 'workflow') {
1200
1473
  throw new Error(`Unknown workflow type: ${workflow.type}`);
1201
1474
  }
1202
-
1203
- this.context = {
1204
- ...inputs,
1205
- workflow_name: workflow.name
1475
+
1476
+ this.context = {
1477
+ ...inputs,
1478
+ workflow_name: workflow.name
1206
1479
  };
1207
1480
 
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
- );
1481
+ // AUDIT LOG: Workflow start (ENHANCED with governance metadata)
1482
+ const governanceHash = this._generateGovernanceProfileHash(workflow);
1483
+
1484
+ this._createAuditEntry('workflow_started', {
1485
+ workflow_id: `${workflow.name}@${workflow.version || 'unversioned'}`, // Workflow ID
1486
+ workflow_name: workflow.name,
1487
+ workflow_version: workflow.version || null,
1488
+ kernel_version: KERNEL_VERSION, // ✅ Kernel Version
1489
+ runtime_version: KERNEL_VERSION, // ✅ Runtime Version (same as kernel for now)
1490
+ governance_profile_hash: governanceHash, // ✅ Governance Profile Hash
1491
+ inputs_count: Object.keys(inputs).length,
1492
+ steps_count: workflow.steps.length,
1493
+ allowed_resolvers: workflow.allowedResolvers || [],
1494
+ constraints: {
1495
+ max_generations: workflow.maxGenerations,
1496
+ strict_inputs: process.env.OLANG_STRICT_INPUTS === 'true'
1497
+ }
1498
+ });
1499
+
1500
+ // Optional strict mode: enforce resolver-originated inputs
1501
+ if (process.env.OLANG_STRICT_INPUTS === 'true') {
1502
+ if (!inputs.__resolver_origin) {
1503
+ throw new Error(
1504
+ '[O-Lang SAFETY] Inputs must originate from a certified resolver. ' +
1505
+ 'Use @o-lang/input-validator to validate external data.'
1506
+ );
1507
+ }
1215
1508
  }
1216
- }
1217
-
1509
+
1218
1510
  const currentGeneration = inputs.__generation || 1;
1219
1511
  if (workflow.maxGenerations !== null && currentGeneration > workflow.maxGenerations) {
1220
1512
  throw new Error(`Workflow generation ${currentGeneration} exceeds Constraint: max_generations = ${workflow.maxGenerations}`);
@@ -1222,10 +1514,8 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1222
1514
 
1223
1515
  this.workflowSteps = workflow.steps;
1224
1516
  this.allowedResolvers = new Set(workflow.allowedResolvers || []);
1225
-
1226
1517
  const mathPattern =
1227
1518
  /^(Add|Subtract|Multiply|Divide|Sum|Avg|Min|Max|Round|Floor|Ceil|Abs)\b/i;
1228
-
1229
1519
  for (const step of workflow.steps) {
1230
1520
  if (step.type === 'calculate' || (step.actionRaw && mathPattern.test(step.actionRaw))) {
1231
1521
  this.allowedResolvers.add('builtInMathResolver');
@@ -1235,9 +1525,23 @@ const isLLMAction = action.toLowerCase().includes('groq') ||
1235
1525
  for (const step of workflow.steps) {
1236
1526
  await this.executeStep(step, agentResolver);
1237
1527
  }
1528
+
1529
+ // ✅ AUDIT LOG: Workflow completion (ENHANCED)
1530
+ this._createAuditEntry('workflow_completed', {
1531
+ workflow_id: `${workflow.name}@${workflow.version || 'unversioned'}`,
1532
+ workflow_name: workflow.name,
1533
+ kernel_version: KERNEL_VERSION,
1534
+ runtime_version: KERNEL_VERSION,
1535
+ governance_profile_hash: governanceHash,
1536
+ return_values: workflow.returnValues,
1537
+ total_steps: workflow.steps.length,
1538
+ execution_summary: {
1539
+ warnings: this.__warnings.length,
1540
+ disallowed_attempts: this.disallowedAttempts.length
1541
+ }
1542
+ });
1238
1543
 
1239
1544
  this.printDisallowedSummary();
1240
-
1241
1545
  if (this.__warnings.length) {
1242
1546
  console.log(`\n[O-Lang] ⚠️ Parser/Runtime Warnings (${this.__warnings.length}):`);
1243
1547
  this.__warnings.slice(0, 5).forEach((w, i) => {