@higher.archi/boe 1.0.7 → 1.0.9

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.
Files changed (125) hide show
  1. package/dist/core/errors.d.ts +48 -0
  2. package/dist/core/errors.d.ts.map +1 -0
  3. package/dist/core/errors.js +63 -0
  4. package/dist/core/errors.js.map +1 -0
  5. package/dist/core/evaluation/decay.d.ts +60 -0
  6. package/dist/core/evaluation/decay.d.ts.map +1 -0
  7. package/dist/core/evaluation/decay.js +111 -0
  8. package/dist/core/evaluation/decay.js.map +1 -0
  9. package/dist/core/evaluation/index.d.ts +2 -0
  10. package/dist/core/evaluation/index.d.ts.map +1 -1
  11. package/dist/core/evaluation/index.js +5 -1
  12. package/dist/core/evaluation/index.js.map +1 -1
  13. package/dist/core/index.d.ts +2 -0
  14. package/dist/core/index.d.ts.map +1 -1
  15. package/dist/core/index.js +6 -1
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/core/types/rule.d.ts +1 -0
  18. package/dist/core/types/rule.d.ts.map +1 -1
  19. package/dist/engines/backward/compiler.js +1 -1
  20. package/dist/engines/backward/compiler.js.map +1 -1
  21. package/dist/engines/bayesian/compiler.d.ts.map +1 -1
  22. package/dist/engines/bayesian/compiler.js +21 -9
  23. package/dist/engines/bayesian/compiler.js.map +1 -1
  24. package/dist/engines/bayesian/strategy.d.ts.map +1 -1
  25. package/dist/engines/bayesian/strategy.js +46 -9
  26. package/dist/engines/bayesian/strategy.js.map +1 -1
  27. package/dist/engines/bayesian/types.d.ts +16 -0
  28. package/dist/engines/bayesian/types.d.ts.map +1 -1
  29. package/dist/engines/bayesian/types.js.map +1 -1
  30. package/dist/engines/constraint/compiler.d.ts.map +1 -1
  31. package/dist/engines/constraint/compiler.js +10 -10
  32. package/dist/engines/constraint/compiler.js.map +1 -1
  33. package/dist/engines/defeasible/compiler.d.ts.map +1 -1
  34. package/dist/engines/defeasible/compiler.js +17 -3
  35. package/dist/engines/defeasible/compiler.js.map +1 -1
  36. package/dist/engines/defeasible/strategy.d.ts.map +1 -1
  37. package/dist/engines/defeasible/strategy.js +47 -2
  38. package/dist/engines/defeasible/strategy.js.map +1 -1
  39. package/dist/engines/defeasible/types.d.ts +29 -1
  40. package/dist/engines/defeasible/types.d.ts.map +1 -1
  41. package/dist/engines/defeasible/types.js.map +1 -1
  42. package/dist/engines/expert/compiler.d.ts.map +1 -1
  43. package/dist/engines/expert/compiler.js +6 -6
  44. package/dist/engines/expert/compiler.js.map +1 -1
  45. package/dist/engines/forward/compiler.d.ts.map +1 -1
  46. package/dist/engines/forward/compiler.js +4 -4
  47. package/dist/engines/forward/compiler.js.map +1 -1
  48. package/dist/engines/forward/strategy.d.ts.map +1 -1
  49. package/dist/engines/forward/strategy.js +4 -4
  50. package/dist/engines/forward/strategy.js.map +1 -1
  51. package/dist/engines/fuzzy/compiler.js +1 -1
  52. package/dist/engines/fuzzy/compiler.js.map +1 -1
  53. package/dist/engines/fuzzy/strategy.d.ts.map +1 -1
  54. package/dist/engines/fuzzy/strategy.js +8 -8
  55. package/dist/engines/fuzzy/strategy.js.map +1 -1
  56. package/dist/engines/monte-carlo/compiler.d.ts.map +1 -1
  57. package/dist/engines/monte-carlo/compiler.js +8 -8
  58. package/dist/engines/monte-carlo/compiler.js.map +1 -1
  59. package/dist/engines/monte-carlo/strategy.js +1 -1
  60. package/dist/engines/monte-carlo/strategy.js.map +1 -1
  61. package/dist/engines/pricing/compiler.d.ts.map +1 -1
  62. package/dist/engines/pricing/compiler.js +9 -9
  63. package/dist/engines/pricing/compiler.js.map +1 -1
  64. package/dist/engines/pricing/strategy.js +7 -6
  65. package/dist/engines/pricing/strategy.js.map +1 -1
  66. package/dist/engines/pricing/types.d.ts +2 -0
  67. package/dist/engines/pricing/types.d.ts.map +1 -1
  68. package/dist/engines/scoring/compiler.d.ts.map +1 -1
  69. package/dist/engines/scoring/compiler.js +24 -10
  70. package/dist/engines/scoring/compiler.js.map +1 -1
  71. package/dist/engines/scoring/strategy.d.ts.map +1 -1
  72. package/dist/engines/scoring/strategy.js +51 -2
  73. package/dist/engines/scoring/strategy.js.map +1 -1
  74. package/dist/engines/scoring/types.d.ts +16 -0
  75. package/dist/engines/scoring/types.d.ts.map +1 -1
  76. package/dist/engines/sequential/compiler.d.ts.map +1 -1
  77. package/dist/engines/sequential/compiler.js +4 -4
  78. package/dist/engines/sequential/compiler.js.map +1 -1
  79. package/dist/engines/sequential/strategy.js +1 -1
  80. package/dist/engines/sequential/strategy.js.map +1 -1
  81. package/dist/engines/state-machine/compiler.js +4 -4
  82. package/dist/engines/state-machine/compiler.js.map +1 -1
  83. package/dist/engines/state-machine/strategy.d.ts.map +1 -1
  84. package/dist/engines/state-machine/strategy.js +2 -1
  85. package/dist/engines/state-machine/strategy.js.map +1 -1
  86. package/dist/engines/utility/compiler.d.ts.map +1 -1
  87. package/dist/engines/utility/compiler.js +7 -7
  88. package/dist/engines/utility/compiler.js.map +1 -1
  89. package/dist/index.d.ts +4 -0
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +10 -1
  92. package/dist/index.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/core/errors.ts +69 -0
  95. package/src/core/evaluation/decay.ts +165 -0
  96. package/src/core/evaluation/index.ts +13 -0
  97. package/src/core/index.ts +4 -0
  98. package/src/core/types/rule.ts +1 -0
  99. package/src/engines/backward/compiler.ts +1 -1
  100. package/src/engines/bayesian/compiler.ts +24 -10
  101. package/src/engines/bayesian/strategy.ts +61 -10
  102. package/src/engines/bayesian/types.ts +17 -0
  103. package/src/engines/constraint/compiler.ts +12 -11
  104. package/src/engines/defeasible/compiler.ts +18 -3
  105. package/src/engines/defeasible/strategy.ts +62 -2
  106. package/src/engines/defeasible/types.ts +33 -0
  107. package/src/engines/expert/compiler.ts +8 -7
  108. package/src/engines/forward/compiler.ts +6 -5
  109. package/src/engines/forward/strategy.ts +6 -5
  110. package/src/engines/fuzzy/compiler.ts +1 -1
  111. package/src/engines/fuzzy/strategy.ts +9 -9
  112. package/src/engines/monte-carlo/compiler.ts +10 -9
  113. package/src/engines/monte-carlo/strategy.ts +2 -2
  114. package/src/engines/pricing/compiler.ts +11 -10
  115. package/src/engines/pricing/strategy.ts +7 -7
  116. package/src/engines/pricing/types.ts +2 -0
  117. package/src/engines/scoring/compiler.ts +27 -11
  118. package/src/engines/scoring/strategy.ts +67 -3
  119. package/src/engines/scoring/types.ts +17 -0
  120. package/src/engines/sequential/compiler.ts +6 -5
  121. package/src/engines/sequential/strategy.ts +2 -2
  122. package/src/engines/state-machine/compiler.ts +5 -5
  123. package/src/engines/state-machine/strategy.ts +3 -1
  124. package/src/engines/utility/compiler.ts +9 -8
  125. package/src/index.ts +16 -0
@@ -17,6 +17,8 @@ import {
17
17
  BindingContext
18
18
  } from '../../core';
19
19
 
20
+ import { DecayConfig, DecayInfo } from '../../core/evaluation/decay';
21
+
20
22
  // ========================================
21
23
  // Likelihood Value Types
22
24
  // ========================================
@@ -100,6 +102,10 @@ export type PriorDefault =
100
102
  */
101
103
  export type BayesianRule = BaseRule & {
102
104
  likelihood: LikelihoodMap; // How this evidence affects each hypothesis
105
+ decay?: {
106
+ timestamp: string | Expression; // e.g. '$sensor.readingDate'
107
+ config?: Partial<DecayConfig>; // overrides engine-level defaults
108
+ };
103
109
  };
104
110
 
105
111
  /**
@@ -109,6 +115,10 @@ export type BayesianConfig = {
109
115
  mode: 'bayesian';
110
116
  threshold?: number; // Flag if any posterior exceeds this (e.g., 0.5)
111
117
  priorDefault?: PriorDefault; // How to fill missing priors (default: 'uniform')
118
+ decay?: {
119
+ timestamp?: string | Expression; // default timestamp path for all rules
120
+ config: DecayConfig; // engine-level decay config
121
+ };
112
122
  };
113
123
 
114
124
  /**
@@ -152,6 +162,10 @@ export type CompiledLikelihoodMap = Record<string, number>;
152
162
  */
153
163
  export type CompiledBayesianRule = CompiledBaseRule & {
154
164
  likelihood: CompiledLikelihoodMap;
165
+ decay?: {
166
+ timestamp: string; // $-prefixed path
167
+ config?: Partial<DecayConfig>;
168
+ };
155
169
  };
156
170
 
157
171
  /**
@@ -176,6 +190,8 @@ export type EvidenceEntry = {
176
190
  ruleId: string;
177
191
  ruleName?: string;
178
192
  likelihoods: CompiledLikelihoodMap;
193
+ likelihoodsBeforeDecay?: CompiledLikelihoodMap; // original likelihoods before decay
194
+ decayInfo?: DecayInfo;
179
195
  };
180
196
 
181
197
  /**
@@ -197,6 +213,7 @@ export type BayesianResult = {
197
213
  fired: string[]; // Rules that matched and provided evidence
198
214
  evidence: EvidenceEntry[]; // Audit trail
199
215
  thresholdExceeded?: string; // First hypothesis to exceed threshold (if any)
216
+ decayInfo?: Record<string, DecayInfo>; // per-rule decay audit
200
217
  iterations: 1; // Bayesian is always one-pass
201
218
  executionTimeMs: number; // Processing time in milliseconds
202
219
  };
@@ -8,7 +8,8 @@
8
8
  import {
9
9
  compileCondition,
10
10
  normalizeShape,
11
- CompiledInputParameter
11
+ CompiledInputParameter,
12
+ CompilationError
12
13
  } from '../../core';
13
14
 
14
15
  import {
@@ -49,11 +50,11 @@ function compileVariable(
49
50
  index: number
50
51
  ): CompiledConstraintVariable {
51
52
  if (!variable.name) {
52
- throw new Error(`Variable at index ${index} must have a name`);
53
+ throw new CompilationError(`Variable at index ${index} must have a name`);
53
54
  }
54
55
 
55
56
  if (!Array.isArray(variable.domain) || variable.domain.length === 0) {
56
- throw new Error(`Variable '${variable.name}' must have a non-empty domain array`);
57
+ throw new CompilationError(`Variable '${variable.name}' must have a non-empty domain array`);
57
58
  }
58
59
 
59
60
  return {
@@ -73,11 +74,11 @@ function compileConstraint(
73
74
  constraintIndex: number
74
75
  ): CompiledConstraint {
75
76
  if (!constraint.id) {
76
- throw new Error(`Constraint at index ${constraintIndex} must have an id`);
77
+ throw new CompilationError(`Constraint at index ${constraintIndex} must have an id`);
77
78
  }
78
79
 
79
80
  if (!Array.isArray(constraint.variables) || constraint.variables.length === 0) {
80
- throw new Error(`Constraint '${constraint.id}' must reference at least one variable`);
81
+ throw new CompilationError(`Constraint '${constraint.id}' must reference at least one variable`);
81
82
  }
82
83
 
83
84
  // Map variable names to indices
@@ -85,7 +86,7 @@ function compileConstraint(
85
86
  for (const varName of constraint.variables) {
86
87
  const index = variableMap.get(varName);
87
88
  if (index === undefined) {
88
- throw new Error(
89
+ throw new CompilationError(
89
90
  `Constraint '${constraint.id}' references unknown variable '${varName}'`
90
91
  );
91
92
  }
@@ -125,11 +126,11 @@ function compilePreference(
125
126
  preferenceIndex: number
126
127
  ): CompiledPreference {
127
128
  if (!preference.id) {
128
- throw new Error(`Preference at index ${preferenceIndex} must have an id`);
129
+ throw new CompilationError(`Preference at index ${preferenceIndex} must have an id`);
129
130
  }
130
131
 
131
132
  if (!Array.isArray(preference.variables) || preference.variables.length === 0) {
132
- throw new Error(`Preference '${preference.id}' must reference at least one variable`);
133
+ throw new CompilationError(`Preference '${preference.id}' must reference at least one variable`);
133
134
  }
134
135
 
135
136
  // Map variable names to indices
@@ -137,7 +138,7 @@ function compilePreference(
137
138
  for (const varName of preference.variables) {
138
139
  const index = variableMap.get(varName);
139
140
  if (index === undefined) {
140
- throw new Error(
141
+ throw new CompilationError(
141
142
  `Preference '${preference.id}' references unknown variable '${varName}'`
142
143
  );
143
144
  }
@@ -211,7 +212,7 @@ export function compileConstraintRuleSet(
211
212
  ): CompiledConstraintRuleSet {
212
213
  // Validate mode
213
214
  if (ruleSet.mode !== 'constraint') {
214
- throw new Error(`Expected mode 'constraint', got '${ruleSet.mode}'`);
215
+ throw new CompilationError(`Expected mode 'constraint', got '${ruleSet.mode}'`);
215
216
  }
216
217
 
217
218
  // Compile variables and build name->index map
@@ -220,7 +221,7 @@ export function compileConstraintRuleSet(
220
221
  const compiled = compileVariable(variable, index);
221
222
 
222
223
  if (variableMap.has(variable.name)) {
223
- throw new Error(`Duplicate variable name: '${variable.name}'`);
224
+ throw new CompilationError(`Duplicate variable name: '${variable.name}'`);
224
225
  }
225
226
  variableMap.set(variable.name, index);
226
227
 
@@ -34,7 +34,8 @@ const DEFAULT_CONFIG: Required<DefeasibleConfig> = {
34
34
  aggregation: 'average',
35
35
  threshold: 0.5,
36
36
  conflictResolution: 'specificity',
37
- maxIterations: 100
37
+ maxIterations: 100,
38
+ decay: undefined as any // Optional at runtime; Required<> demands a key
38
39
  };
39
40
 
40
41
  // ========================================
@@ -198,6 +199,19 @@ function compileRule(rule: DefeasibleRule, index: number): CompiledDefeasibleRul
198
199
  const inputs = (rule.inputs || []).map(compileInputParameter);
199
200
  const when = rule.when ? compileCondition(rule.when) : undefined;
200
201
 
202
+ // Compile decay — only pass through $-prefixed string timestamps
203
+ let compiledDecay: CompiledDefeasibleRule['decay'] | undefined;
204
+ if (rule.decay) {
205
+ const ts = rule.decay.timestamp;
206
+ if (typeof ts === 'string') {
207
+ compiledDecay = {
208
+ timestamp: ts,
209
+ config: rule.decay.config,
210
+ target: rule.decay.target
211
+ };
212
+ }
213
+ }
214
+
201
215
  return {
202
216
  id: rule.id,
203
217
  name: rule.name,
@@ -210,7 +224,8 @@ function compileRule(rule: DefeasibleRule, index: number): CompiledDefeasibleRul
210
224
  credibility: normalizeCredibility(rule.credibility),
211
225
  conclusion: compileConclusion(rule.conclude),
212
226
  defeats: (rule.defeats || []).map(normalizeDefeat),
213
- specificity: countSpecificity(rule)
227
+ specificity: countSpecificity(rule),
228
+ decay: compiledDecay
214
229
  };
215
230
  }
216
231
 
@@ -288,7 +303,7 @@ export function compileDefeasibleRuleSet(ruleSet: DefeasibleRuleSet): CompiledDe
288
303
  const inputs = (ruleSet.inputs || []).map(compileInputParameter);
289
304
 
290
305
  // Compile rules
291
- const rules = ruleSet.rules.map((rule, index) => compileRule(rule, index));
306
+ const rules = ruleSet.rules.filter(r => !r.disabled).map((rule, index) => compileRule(rule, index));
292
307
 
293
308
  // Build superiority map
294
309
  const superiority = buildSuperiorityMap(rules, ruleSet.superiority);
@@ -7,6 +7,12 @@
7
7
 
8
8
  import { match } from '../../core/matching';
9
9
  import { IWorkingMemory } from '../../core/memory';
10
+ import {
11
+ calculateDecayMultiplier,
12
+ resolveDecayTimestamp,
13
+ DecayConfig,
14
+ DecayInfo
15
+ } from '../../core/evaluation/decay';
10
16
 
11
17
  import {
12
18
  CompiledDefeasibleRuleSet,
@@ -101,17 +107,57 @@ export class DefeasibleStrategy {
101
107
  // Phase 1: Match all rules and collect fired rules
102
108
  const firedRules: FiredRule[] = [];
103
109
  const firedRuleIds: string[] = [];
110
+ const decayInfoMap: Record<string, DecayInfo> = {};
111
+ const decayMultipliersByRule: Record<string, number> = {};
112
+ const now = new Date();
104
113
 
105
114
  for (const rule of ruleSet.rules) {
106
115
  // Match inputs against working memory (includes when clause evaluation)
107
116
  const activations = match(rule, wm);
108
117
 
109
118
  for (const activation of activations) {
119
+ // Compute decay if configured
120
+ let effectiveCredibility = rule.credibility;
121
+ let credibilityBeforeDecay: number | undefined;
122
+ let ruleDecayInfo: DecayInfo | undefined;
123
+
124
+ const ruleDecay = rule.decay;
125
+ const engineDecay = config.decay;
126
+
127
+ if (ruleDecay || engineDecay) {
128
+ const tsRef = ruleDecay?.timestamp ?? (engineDecay?.timestamp as string | undefined);
129
+ const decayTarget = ruleDecay?.target ?? engineDecay?.target ?? 'credibility';
130
+
131
+ if (typeof tsRef === 'string') {
132
+ const dataTimestamp = resolveDecayTimestamp(tsRef, activation.facts);
133
+
134
+ if (dataTimestamp) {
135
+ const decayConfig: DecayConfig = {
136
+ ...(engineDecay?.config ?? { curve: 'exponential', timeUnit: 'days' }),
137
+ ...ruleDecay?.config
138
+ } as DecayConfig;
139
+
140
+ const info = calculateDecayMultiplier(dataTimestamp, now, decayConfig);
141
+ ruleDecayInfo = info;
142
+ decayInfoMap[rule.id] = info;
143
+ decayMultipliersByRule[rule.id] = info.multiplier;
144
+
145
+ // Apply decay to credibility if target includes it
146
+ if (decayTarget === 'credibility' || decayTarget === 'both') {
147
+ credibilityBeforeDecay = rule.credibility;
148
+ effectiveCredibility = rule.credibility * info.multiplier;
149
+ }
150
+ }
151
+ }
152
+ }
153
+
110
154
  // Rule fires
111
155
  firedRules.push({
112
156
  ruleId: rule.id,
113
157
  conclusion: rule.conclusion,
114
- credibility: rule.credibility,
158
+ credibility: effectiveCredibility,
159
+ credibilityBeforeDecay,
160
+ decayInfo: ruleDecayInfo,
115
161
  bindings: activation.facts
116
162
  });
117
163
 
@@ -166,9 +212,21 @@ export class DefeasibleStrategy {
166
212
  if (!defeatsMap.has(defeat.rule)) {
167
213
  defeatsMap.set(defeat.rule, []);
168
214
  }
215
+
216
+ // Apply decay to defeat strength if target includes it
217
+ let effectiveStrength = defeat.strength;
218
+ const ruleDecay = rule.decay;
219
+ const engineDecay = config.decay;
220
+ const decayTarget = ruleDecay?.target ?? engineDecay?.target ?? 'credibility';
221
+
222
+ if ((decayTarget === 'defeat-strength' || decayTarget === 'both') &&
223
+ decayMultipliersByRule[fired.ruleId] !== undefined) {
224
+ effectiveStrength = defeat.strength * decayMultipliersByRule[fired.ruleId];
225
+ }
226
+
169
227
  defeatsMap.get(defeat.rule)!.push({
170
228
  by: fired.ruleId,
171
- strength: defeat.strength
229
+ strength: effectiveStrength
172
230
  });
173
231
  }
174
232
  }
@@ -291,6 +349,7 @@ export class DefeasibleStrategy {
291
349
  };
292
350
 
293
351
  const executionTimeMs = Math.round((performance.now() - startTime) * 100) / 100;
352
+ const hasDecay = Object.keys(decayInfoMap).length > 0;
294
353
 
295
354
  return {
296
355
  conclusions,
@@ -300,6 +359,7 @@ export class DefeasibleStrategy {
300
359
  conflicts,
301
360
  fired: firedRuleIds,
302
361
  iterations: 1, // Current implementation is single-pass
362
+ ...(hasDecay ? { decayInfo: decayInfoMap } : {}),
303
363
  executionTimeMs,
304
364
  query
305
365
  };
@@ -20,9 +20,12 @@ import {
20
20
  CompiledBaseRule,
21
21
  CompiledBaseRuleSet,
22
22
  CompiledInputParameter,
23
+ Expression,
23
24
  BindingContext
24
25
  } from '../../core';
25
26
 
27
+ import { DecayConfig, DecayInfo } from '../../core/evaluation/decay';
28
+
26
29
  // ========================================
27
30
  // Rule Strength Types
28
31
  // ========================================
@@ -167,6 +170,15 @@ export type DefeasibleRule = BaseRule & {
167
170
  * Useful for audit trails and explainability.
168
171
  */
169
172
  explanation?: string;
173
+
174
+ /**
175
+ * Temporal decay configuration for this rule
176
+ */
177
+ decay?: {
178
+ timestamp: string | Expression;
179
+ config?: Partial<DecayConfig>;
180
+ target?: 'credibility' | 'defeat-strength' | 'both'; // default: 'credibility'
181
+ };
170
182
  };
171
183
 
172
184
  /**
@@ -216,6 +228,15 @@ export type DefeasibleConfig = {
216
228
  * Maximum iterations for computing defeat cycles (default: 100)
217
229
  */
218
230
  maxIterations?: number;
231
+
232
+ /**
233
+ * Engine-level temporal decay configuration
234
+ */
235
+ decay?: {
236
+ timestamp?: string | Expression;
237
+ config: DecayConfig;
238
+ target?: 'credibility' | 'defeat-strength' | 'both';
239
+ };
219
240
  };
220
241
 
221
242
  /**
@@ -263,6 +284,11 @@ export type CompiledDefeasibleRule = CompiledBaseRule & {
263
284
  conclusion: CompiledConclusion | null; // null for pure defeaters
264
285
  defeats: CompiledDefeat[]; // Normalized defeats
265
286
  specificity: number; // Number of conditions (for tie-breaking)
287
+ decay?: {
288
+ timestamp: string; // $-prefixed path
289
+ config?: Partial<DecayConfig>;
290
+ target?: 'credibility' | 'defeat-strength' | 'both';
291
+ };
266
292
  };
267
293
 
268
294
  /**
@@ -293,6 +319,8 @@ export type FiredRule = {
293
319
  ruleId: string;
294
320
  conclusion: CompiledConclusion | null;
295
321
  credibility: number;
322
+ credibilityBeforeDecay?: number; // original credibility before decay
323
+ decayInfo?: DecayInfo;
296
324
  bindings: BindingContext;
297
325
  };
298
326
 
@@ -385,6 +413,11 @@ export type DefeasibleResult = {
385
413
  */
386
414
  iterations: number;
387
415
 
416
+ /**
417
+ * Per-rule decay audit trail
418
+ */
419
+ decayInfo?: Record<string, DecayInfo>;
420
+
388
421
  /**
389
422
  * Processing time in milliseconds
390
423
  */
@@ -8,7 +8,8 @@
8
8
  import {
9
9
  compileCondition,
10
10
  compileOperand,
11
- normalizeShape
11
+ normalizeShape,
12
+ CompilationError
12
13
  } from '../../core';
13
14
 
14
15
  import {
@@ -42,7 +43,7 @@ function normalizeConfidence(confidence: Confidence): number {
42
43
  }
43
44
  const value = SEMANTIC_CONFIDENCES[confidence as SemanticConfidence];
44
45
  if (value === undefined) {
45
- throw new Error(`Invalid semantic confidence: '${confidence}'. Valid values: ${Object.keys(SEMANTIC_CONFIDENCES).join(', ')}`);
46
+ throw new CompilationError(`Invalid semantic confidence: '${confidence}'. Valid values: ${Object.keys(SEMANTIC_CONFIDENCES).join(', ')}`);
46
47
  }
47
48
  return value;
48
49
  }
@@ -179,13 +180,13 @@ function compileExpertRule(
179
180
  ): CompiledExpertRule {
180
181
  // Validate required fields
181
182
  if (!rule.description) {
182
- throw new Error(`Rule '${rule.id}' is missing required 'description' field`);
183
+ throw new CompilationError(`Rule '${rule.id}' is missing required 'description' field`);
183
184
  }
184
185
  if (!rule.explain) {
185
- throw new Error(`Rule '${rule.id}' is missing required 'explain' block`);
186
+ throw new CompilationError(`Rule '${rule.id}' is missing required 'explain' block`);
186
187
  }
187
188
  if (!rule.explain.onFire) {
188
- throw new Error(`Rule '${rule.id}' explain block is missing required 'onFire' template`);
189
+ throw new CompilationError(`Rule '${rule.id}' explain block is missing required 'onFire' template`);
189
190
  }
190
191
 
191
192
  // Normalize confidence (handles both numeric and semantic values)
@@ -244,14 +245,14 @@ function compileExpertRule(
244
245
  export function compileExpertRuleSet(ruleSet: ExpertRuleSet): CompiledExpertRuleSet {
245
246
  // Validate mode
246
247
  if (ruleSet.mode !== 'expert') {
247
- throw new Error(`Invalid mode '${ruleSet.mode}' for expert ruleset`);
248
+ throw new CompilationError(`Invalid mode '${ruleSet.mode}' for expert ruleset`);
248
249
  }
249
250
 
250
251
  // Collect all derived facts for reference validation
251
252
  const derivedFacts = collectDerivedFacts(ruleSet.rules);
252
253
 
253
254
  // Compile all rules
254
- const compiledRules = ruleSet.rules.map((rule, index) =>
255
+ const compiledRules = ruleSet.rules.filter(r => !r.disabled).map((rule, index) =>
255
256
  compileExpertRule(rule, index, derivedFacts)
256
257
  );
257
258
 
@@ -9,7 +9,8 @@ import {
9
9
  normalizeShape,
10
10
  DEFAULT_STRATEGIES,
11
11
  Condition,
12
- PropertyRef
12
+ PropertyRef,
13
+ CompilationError
13
14
  } from '../../core';
14
15
 
15
16
  import {
@@ -77,7 +78,7 @@ function compileActionItem(item: ActionItem): CompiledActionItem {
77
78
 
78
79
  for (const [targetPath, value] of Object.entries(item.set)) {
79
80
  if (!isPropertyRef(targetPath)) {
80
- throw new Error(`Set target must be a property reference (e.g., '$person.status'), got: ${targetPath}`);
81
+ throw new CompilationError(`Set target must be a property reference (e.g., '$person.status'), got: ${targetPath}`);
81
82
  }
82
83
 
83
84
  // Check if value is an expression that needs to be compiled as a condition
@@ -102,7 +103,7 @@ function compileActionItem(item: ActionItem): CompiledActionItem {
102
103
  if ('retract' in item) {
103
104
  const ref = item.retract;
104
105
  if (!isPropertyRef(ref)) {
105
- throw new Error(`Retract must be a property reference (e.g., '$duplicate'), got: ${ref}`);
106
+ throw new CompilationError(`Retract must be a property reference (e.g., '$duplicate'), got: ${ref}`);
106
107
  }
107
108
  // Extract just the parameter name (first part after $)
108
109
  const paramName = ref.slice(1).split('.')[0];
@@ -119,7 +120,7 @@ function compileActionItem(item: ActionItem): CompiledActionItem {
119
120
  return { halt: true };
120
121
  }
121
122
 
122
- throw new Error(`Unknown action item type: ${JSON.stringify(item)}`);
123
+ throw new CompilationError(`Unknown action item type: ${JSON.stringify(item)}`);
123
124
  }
124
125
 
125
126
  function normalizeThenValue(value: ThenValue): ActionItem[] {
@@ -199,7 +200,7 @@ export function compileForwardRuleSet(ruleSet: ForwardRuleSet): CompiledForwardR
199
200
  name: ruleSet.name,
200
201
  mode: 'forward',
201
202
  schema: ruleSet.shape ? normalizeShape(ruleSet.shape) : { type: 'object' },
202
- rules: ruleSet.rules.map((rule, index) => compileForwardRule(rule, index)),
203
+ rules: ruleSet.rules.filter(r => !r.disabled).map((rule, index) => compileForwardRule(rule, index)),
203
204
  config
204
205
  };
205
206
  }
@@ -9,7 +9,8 @@ import {
9
9
  resolveOperand,
10
10
  setValueAtPath,
11
11
  evaluateExpression,
12
- BindingContext
12
+ BindingContext,
13
+ RuntimeError
13
14
  } from '../../core';
14
15
  import { IWorkingMemory } from '../../core/memory';
15
16
 
@@ -105,7 +106,7 @@ export class ForwardChainingStrategy {
105
106
  for (const { target, value, expr } of item.set) {
106
107
  const fact = context[target.param];
107
108
  if (!fact) {
108
- throw new Error(`Cannot set property on unknown fact: ${target.param}`);
109
+ throw new RuntimeError(`Cannot set property on unknown fact: ${target.param}`);
109
110
  }
110
111
 
111
112
  // Resolve the value (could be constant, property ref, or expression)
@@ -119,7 +120,7 @@ export class ForwardChainingStrategy {
119
120
  resolvedValue = resolveOperand(value, context);
120
121
  } else {
121
122
  // Function operand - not yet supported
122
- throw new Error('Function operands in set actions are not yet supported');
123
+ throw new RuntimeError('Function operands in set actions are not yet supported');
123
124
  }
124
125
 
125
126
  // Set the value at the target path (auto-creates intermediate objects)
@@ -138,7 +139,7 @@ export class ForwardChainingStrategy {
138
139
  if ('retract' in item) {
139
140
  const fact = context[item.retract];
140
141
  if (!fact) {
141
- throw new Error(`Cannot retract unknown fact: ${item.retract}`);
142
+ throw new RuntimeError(`Cannot retract unknown fact: ${item.retract}`);
142
143
  }
143
144
  refraction.retractByFactId(fact.id);
144
145
  wm.remove(fact.id);
@@ -149,7 +150,7 @@ export class ForwardChainingStrategy {
149
150
  if ('call' in item) {
150
151
  const handler = actions[item.call];
151
152
  if (!handler) {
152
- throw new Error(`Action handler not found: ${item.call}`);
153
+ throw new RuntimeError(`Action handler not found: ${item.call}`);
153
154
  }
154
155
 
155
156
  const result = handler(context, wm);
@@ -174,7 +174,7 @@ export function compileFuzzyRuleSet(ruleSet: FuzzyRuleSet): CompiledFuzzyRuleSet
174
174
  name: ruleSet.name,
175
175
  mode: 'fuzzy',
176
176
  schema: ruleSet.shape ? normalizeShape(ruleSet.shape) : { type: 'object' },
177
- rules: ruleSet.rules.map((rule, index) => compileFuzzyRule(rule, index)),
177
+ rules: ruleSet.rules.filter(r => !r.disabled).map((rule, index) => compileFuzzyRule(rule, index)),
178
178
  config,
179
179
  variables,
180
180
  variableIndex
@@ -5,7 +5,7 @@
5
5
  * Self-contained within the fuzzy engine.
6
6
  */
7
7
 
8
- import { IWorkingMemory, BindingContext, evaluateCondition, matchInputsOnly } from '../../core';
8
+ import { IWorkingMemory, BindingContext, evaluateCondition, matchInputsOnly, RuntimeError } from '../../core';
9
9
  import { CompiledFuzzyRuleSet, CompiledFuzzyRule } from './compiler';
10
10
  import {
11
11
  FuzzyOptions,
@@ -54,7 +54,7 @@ export function evaluateFuzzyIs(
54
54
  const variable = ctx.variables.get(varName);
55
55
 
56
56
  if (!variable) {
57
- throw new Error(`Fuzzy variable '${varName}' not found`);
57
+ throw new RuntimeError(`Fuzzy variable '${varName}' not found`);
58
58
  }
59
59
 
60
60
  return getMembershipDegree(value, variable, termName);
@@ -86,7 +86,7 @@ export class FuzzyStrategy {
86
86
 
87
87
  // Ensure ruleset is fuzzy mode
88
88
  if (ruleSet.mode !== 'fuzzy') {
89
- throw new Error(`FuzzyStrategy requires mode='fuzzy', got '${ruleSet.mode}'`);
89
+ throw new RuntimeError(`FuzzyStrategy requires mode='fuzzy', got '${ruleSet.mode}'`);
90
90
  }
91
91
 
92
92
  const { onFire } = options;
@@ -171,7 +171,7 @@ export class FuzzyStrategy {
171
171
  for (const [varName, termMemberships] of Object.entries(aggregated)) {
172
172
  const variable = ruleSet.variableIndex.get(varName);
173
173
  if (!variable) {
174
- throw new Error(`Output variable '${varName}' not found`);
174
+ throw new RuntimeError(`Output variable '${varName}' not found`);
175
175
  }
176
176
 
177
177
  const crispValue = executePipeline(defuzzifyConfig, {
@@ -262,7 +262,7 @@ export class FuzzyStrategy {
262
262
  const variable = variables.get(varName);
263
263
 
264
264
  if (!variable) {
265
- throw new Error(`Fuzzy variable '${varName}' not found`);
265
+ throw new RuntimeError(`Fuzzy variable '${varName}' not found`);
266
266
  }
267
267
 
268
268
  const degree = getMembershipDegree(value, variable, termName);
@@ -284,7 +284,7 @@ export class FuzzyStrategy {
284
284
  const variable = variables.get(varName);
285
285
 
286
286
  if (!variable) {
287
- throw new Error(`Fuzzy variable '${varName}' not found`);
287
+ throw new RuntimeError(`Fuzzy variable '${varName}' not found`);
288
288
  }
289
289
 
290
290
  const degree = 1 - getMembershipDegree(value, variable, termName);
@@ -350,7 +350,7 @@ export class FuzzyStrategy {
350
350
  if (operand.type === 'property') {
351
351
  const fact = bindings[operand.ref.param];
352
352
  if (!fact) {
353
- throw new Error(`Fact '${operand.ref.param}' not found in bindings`);
353
+ throw new RuntimeError(`Fact '${operand.ref.param}' not found in bindings`);
354
354
  }
355
355
 
356
356
  let value = fact.data;
@@ -359,13 +359,13 @@ export class FuzzyStrategy {
359
359
  }
360
360
 
361
361
  if (typeof value !== 'number') {
362
- throw new Error(`Fuzzy membership requires numeric value, got ${typeof value}`);
362
+ throw new RuntimeError(`Fuzzy membership requires numeric value, got ${typeof value}`);
363
363
  }
364
364
 
365
365
  return value;
366
366
  }
367
367
 
368
- throw new Error(`Unknown operand type: ${operand.type}`);
368
+ throw new RuntimeError(`Unknown operand type: ${operand.type}`);
369
369
  }
370
370
  }
371
371
 
@@ -8,7 +8,8 @@
8
8
  import {
9
9
  compileCondition,
10
10
  normalizeShape,
11
- Expression
11
+ Expression,
12
+ CompilationError
12
13
  } from '../../core';
13
14
 
14
15
  import {
@@ -104,7 +105,7 @@ function getDistributionType(estimate: string): 'triangular' | 'normal' | 'unifo
104
105
  return 'discrete';
105
106
 
106
107
  default:
107
- throw new Error(`Unknown estimate type: ${estimate}`);
108
+ throw new CompilationError(`Unknown estimate type: ${estimate}`);
108
109
  }
109
110
  }
110
111
 
@@ -134,7 +135,7 @@ function compileTriangularVariable(variable: MonteCarloVariable): CompiledVariab
134
135
  mode = parsePercentOrNumber(v.realistic);
135
136
  max = parsePercentOrNumber(v.pessimistic);
136
137
  } else {
137
- throw new Error(`Invalid triangular variable: ${variable.name}`);
138
+ throw new CompilationError(`Invalid triangular variable: ${variable.name}`);
138
139
  }
139
140
 
140
141
  return {
@@ -155,10 +156,10 @@ function compileNormalVariable(variable: MonteCarloVariable): CompiledVariable {
155
156
  const stdDev = parsePercentOrNumber(v.stdDev ?? v.spread ?? v.varies);
156
157
 
157
158
  if (mean === undefined || isNaN(mean)) {
158
- throw new Error(`Normal variable '${variable.name}' requires mean or value`);
159
+ throw new CompilationError(`Normal variable '${variable.name}' requires mean or value`);
159
160
  }
160
161
  if (stdDev === undefined || isNaN(stdDev)) {
161
- throw new Error(`Normal variable '${variable.name}' requires stdDev, spread, or varies`);
162
+ throw new CompilationError(`Normal variable '${variable.name}' requires stdDev, spread, or varies`);
162
163
  }
163
164
 
164
165
  return {
@@ -179,10 +180,10 @@ function compileUniformVariable(variable: MonteCarloVariable): CompiledVariable
179
180
  const max = parsePercentOrNumber(v.max ?? v.high);
180
181
 
181
182
  if (min === undefined || isNaN(min)) {
182
- throw new Error(`Uniform variable '${variable.name}' requires min or low`);
183
+ throw new CompilationError(`Uniform variable '${variable.name}' requires min or low`);
183
184
  }
184
185
  if (max === undefined || isNaN(max)) {
185
- throw new Error(`Uniform variable '${variable.name}' requires max or high`);
186
+ throw new CompilationError(`Uniform variable '${variable.name}' requires max or high`);
186
187
  }
187
188
 
188
189
  return {
@@ -199,7 +200,7 @@ function compileDiscreteVariable(variable: MonteCarloVariable): CompiledVariable
199
200
  const v = variable as any;
200
201
 
201
202
  if (!v.options || !Array.isArray(v.options)) {
202
- throw new Error(`Discrete variable '${variable.name}' requires options array`);
203
+ throw new CompilationError(`Discrete variable '${variable.name}' requires options array`);
203
204
  }
204
205
 
205
206
  // Default to equal weights if not specified
@@ -235,7 +236,7 @@ function compileVariable(variable: MonteCarloVariable): CompiledVariable {
235
236
  case 'discrete':
236
237
  return compileDiscreteVariable(variable);
237
238
  default:
238
- throw new Error(`Unknown distribution: ${distribution}`);
239
+ throw new CompilationError(`Unknown distribution: ${distribution}`);
239
240
  }
240
241
  }
241
242