@o-lang/olang 1.2.28 → 1.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.2.28",
3
+ "version": "1.2.30",
4
4
  "author": "Olalekan Ogundipe <info@olang.cloud>",
5
5
  "description": "O-Lang: A governance language for user-directed, rule-enforced agent workflows",
6
6
  "main": "./src/runtime/index.js",
@@ -305,6 +305,20 @@ function parseWorkflowLines(lines, filename) {
305
305
  continue;
306
306
  }
307
307
 
308
+ // ✅ ADD: Set keyword (e.g., Set analysis_result = "")
309
+ const setMatch = line.match(/^Set\s+(\w+)\s*=\s*(.+)$/i);
310
+ if (setMatch) {
311
+ flushCurrentStep();
312
+ workflow.steps.push({
313
+ type: 'calculate',
314
+ stepNumber: workflow.steps.length + 1,
315
+ actionRaw: setMatch[2].trim(), // e.g., '""' or '"N/A"'
316
+ saveAs: setMatch[1].trim(), // e.g., 'analysis_result'
317
+ constraints: {}
318
+ });
319
+ continue;
320
+ }
321
+
308
322
  // Debrief
309
323
  const debriefMatch = line.match(/^Debrief\s+([^\s]+)\s+with\s+"([^"]*)"$/i);
310
324
  if (debriefMatch) {
@@ -589,6 +603,18 @@ function parseBlock(lines) {
589
603
  continue;
590
604
  }
591
605
 
606
+ // ✅ ADD: Set keyword inside blocks (e.g., Set analysis_result = "")
607
+ const setMatch = line.match(/^Set\s+(\w+)\s*=\s*(.+)$/i);
608
+ if (setMatch) {
609
+ flush(); // Flush any pending step
610
+ steps.push({
611
+ type: 'calculate',
612
+ actionRaw: setMatch[2].trim(), // e.g., '""' or '"N/A"'
613
+ saveAs: setMatch[1].trim(), // e.g., 'analysis_result'
614
+ constraints: {}
615
+ });
616
+ continue;
617
+ }
592
618
  // Fallback
593
619
  if (current) {
594
620
  current.actionRaw += ' ' + line; // ← PRESERVED EXACTLY (no normalizeAction)
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const crypto = require('crypto'); // ✅ CRYPTOGRAPHIC AUDIT LOGS
4
4
 
5
5
  // ✅ O-Lang Kernel Version (Safety Logic & Governance Rules)
6
- const KERNEL_VERSION = '1.2.20-alpha'; // 🔁 Update when safety rules change
6
+ const KERNEL_VERSION = '1.2.30-alpha'; // 🔁 Update when safety rules change
7
7
 
8
8
  class RuntimeAPI {
9
9
  constructor({ verbose = false } = {}) {
@@ -638,17 +638,38 @@ class RuntimeAPI {
638
638
  return path.split('.').reduce((o, k) => (o && o[k] !== undefined ? o[k] : undefined), obj);
639
639
  }
640
640
 
641
- evaluateCondition(cond, ctx) {
641
+ evaluateCondition(cond, ctx) {
642
642
  cond = cond.trim();
643
- const eq = cond.match(/^\{(.+)\}\s+equals\s+"(.*)"$/);
644
- if (eq) return this.getNested(ctx, eq[1]) == eq[2];
643
+
644
+ // 1. Handle Logical OR (|| or 'or')
645
+ if (/\|\||\bor\b/i.test(cond)) {
646
+ return cond.split(/\|\||\bor\b/i).some(c => this.evaluateCondition(c.trim(), ctx));
647
+ }
648
+ // ✅ 2. Handle Logical AND (&& or 'and')
649
+ if (/&&|\band\b/i.test(cond)) {
650
+ return cond.split(/&&|\band\b/i).every(c => this.evaluateCondition(c.trim(), ctx));
651
+ }
652
+
653
+ // ✅ 3. Handle == or === (works with or without {})
654
+ const eqMatch = cond.match(/^(?:\{(.+)\}|(\w+))\s*===?\s*"(.*)"$/);
655
+ if (eqMatch) {
656
+ const key = eqMatch[1] || eqMatch[2];
657
+ return this.getNested(ctx, key) === eqMatch[3];
658
+ }
659
+
660
+ // ✅ 4. Keep original O-Lang syntax
661
+ const oldEq = cond.match(/^\{(.+)\}\s+equals\s+"(.*)"$/);
662
+ if (oldEq) return this.getNested(ctx, oldEq[1]) == oldEq[2];
663
+
645
664
  const gt = cond.match(/^\{(.+)\}\s+greater than\s+(\d+\.?\d*)$/);
646
665
  if (gt) return parseFloat(this.getNested(ctx, gt[1])) > parseFloat(gt[2]);
666
+
647
667
  const lt = cond.match(/^\{(.+)\}\s+less than\s+(\d+\.?\d*)$/);
648
668
  if (lt) return parseFloat(this.getNested(ctx, lt[1])) < parseFloat(lt[2]);
669
+
670
+ // Fallback: truthy check
649
671
  return Boolean(this.getNested(ctx, cond.replace(/\{|\}/g, '')));
650
672
  }
651
-
652
673
  mathFunctions = {
653
674
  add: (a, b) => a + b,
654
675
  subtract: (a, b) => a - b,
@@ -857,41 +878,57 @@ class RuntimeAPI {
857
878
  if (!output || typeof output !== 'string') return { passed: true };
858
879
 
859
880
  // ── __verified_intent takes priority ──────────────────────────────────────
860
- // If the workflow author has defined intent rules, use those exclusively.
861
- // This makes governance dynamic — skip hardcoded patterns entirely.
862
- const intent = this.context.__verified_intent;
863
- if (intent) {
864
- if (intent.prohibited_actions && Array.isArray(intent.prohibited_actions)) {
865
- const lower = output.toLowerCase();
866
- for (const action of intent.prohibited_actions) {
867
- if (lower.includes(action.toLowerCase())) {
868
- return {
869
- passed: false,
870
- reason: `Output violates prohibited action "${action}" defined in __verified_intent`,
871
- detected: action,
872
- language: 'multi'
873
- };
874
- }
875
- }
881
+ const intent = this.context.__verified_intent;
882
+ if (intent) {
883
+ if (intent.prohibited_actions && Array.isArray(intent.prohibited_actions)) {
884
+ const lower = output.toLowerCase();
885
+ for (const action of intent.prohibited_actions) {
886
+ if (lower.includes(action.toLowerCase())) {
887
+ return {
888
+ passed: false,
889
+ reason: `Output violates prohibited action "${action}" defined in __verified_intent`,
890
+ detected: action,
891
+ language: 'multi'
892
+ };
876
893
  }
894
+ }
895
+ }
877
896
 
878
- if (intent.prohibited_topics && Array.isArray(intent.prohibited_topics)) {
879
- const lower = output.toLowerCase();
880
- for (const topic of intent.prohibited_topics) {
881
- if (lower.includes(topic.toLowerCase())) {
882
- return {
883
- passed: false,
884
- reason: `Output violates prohibited topic "${topic}" defined in __verified_intent`,
885
- detected: topic,
886
- language: 'multi'
887
- };
888
- }
897
+ if (intent.prohibited_topics && Array.isArray(intent.prohibited_topics)) {
898
+ for (const topic of intent.prohibited_topics) {
899
+ const isRegex = typeof topic === 'object' && topic.pattern;
900
+ let matched = false;
901
+ let detected = '';
902
+
903
+ if (isRegex) {
904
+ try {
905
+ const re = new RegExp(topic.pattern, topic.flags || 'i');
906
+ const match = output.match(re);
907
+ matched = !!match;
908
+ detected = match ? match[0] : topic.pattern;
909
+ } catch (e) {
910
+ this.addWarning(`Invalid prohibited_topic regex: "${topic.pattern}" — ${e.message}`);
911
+ continue;
889
912
  }
913
+ } else {
914
+ matched = output.toLowerCase().includes(topic.toLowerCase());
915
+ detected = topic;
890
916
  }
891
917
 
892
- // __verified_intent present and passed — skip hardcoded patterns
893
- return { passed: true };
918
+ if (matched) {
919
+ return {
920
+ passed: false,
921
+ reason: `Output violates prohibited topic "${isRegex ? topic.pattern : topic}" defined in __verified_intent`,
922
+ detected,
923
+ language: 'multi'
924
+ };
925
+ }
894
926
  }
927
+ }
928
+
929
+ // __verified_intent present and passed — skip hardcoded patterns
930
+ return { passed: true };
931
+ }
895
932
 
896
933
  // ── No __verified_intent — fall through to hardcoded patterns ─────────────
897
934
  // 🔑 Extract allowed capabilities from workflow allowlist