@run-iq/plugin-sdk 0.1.1 → 0.2.1

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 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/base/BasePlugin.ts","../src/base/BaseModel.ts","../src/testing/PluginTester.ts","../src/validation/SchemaValidator.ts"],"sourcesContent":["export { BasePlugin } from './base/BasePlugin.js';\nexport { BaseModel } from './base/BaseModel.js';\nexport { PluginTester } from './testing/PluginTester.js';\nexport type { TestReport } from './testing/PluginTester.js';\nexport { SchemaValidator } from './validation/SchemaValidator.js';\nexport type { SchemaDefinition, SchemaFieldDef } from './validation/SchemaValidator.js';\nexport type {\n Rule,\n Expression,\n EvaluationInput,\n EvaluationResult,\n BreakdownItem,\n SkippedRule,\n SkipReason,\n PPEPlugin,\n PluginContext,\n CalculationModel,\n ValidationResult,\n DSLEvaluator,\n ISnapshotAdapter,\n Snapshot,\n EvaluationTrace,\n TraceStep,\n} from './types/index.js';\n","import type {\n PPEPlugin,\n PluginContext,\n EvaluationInput,\n EvaluationResult,\n Rule,\n CalculationModel,\n PPEError,\n} from '@run-iq/core';\n\nexport abstract class BasePlugin implements PPEPlugin {\n abstract readonly name: string;\n abstract readonly version: string;\n abstract readonly models: CalculationModel[];\n\n onInit(context: PluginContext): void {\n for (const model of this.models) {\n context.modelRegistry.register(model);\n }\n }\n\n beforeEvaluate(input: EvaluationInput, _rules: ReadonlyArray<Rule>): EvaluationInput {\n return input;\n }\n\n afterEvaluate(_input: EvaluationInput, result: EvaluationResult): EvaluationResult {\n return result;\n }\n\n onError(_error: PPEError, _input: EvaluationInput): void {\n // no-op by default\n }\n\n teardown(): void {\n // no-op by default\n }\n}\n","import type { CalculationModel, ValidationResult, Rule } from '@run-iq/core';\n\nexport abstract class BaseModel implements CalculationModel {\n abstract readonly name: string;\n abstract readonly version: string;\n\n abstract validateParams(params: unknown): ValidationResult;\n\n abstract calculate(\n input: Record<string, unknown>,\n matchedRule: Readonly<Rule>,\n params: unknown,\n ): number;\n}\n","import type {\n PPEPlugin,\n PluginContext,\n EvaluationInput,\n Rule,\n CalculationModel,\n} from '@run-iq/core';\n\nexport interface TestReport {\n readonly passed: string[];\n readonly failed: Array<{ test: string; error: string }>;\n readonly summary: 'PASS' | 'FAIL';\n}\n\nexport class PluginTester {\n private readonly plugin: PPEPlugin;\n\n constructor(plugin: PPEPlugin) {\n this.plugin = plugin;\n }\n\n async assertDeterminism(\n input: EvaluationInput,\n rules: ReadonlyArray<Rule>,\n context: PluginContext,\n ): Promise<void> {\n this.plugin.onInit(context);\n\n const results: EvaluationInput[] = [];\n for (let i = 0; i < 3; i++) {\n if (this.plugin.beforeEvaluate) {\n results.push(this.plugin.beforeEvaluate(input, rules));\n }\n }\n\n if (results.length === 3) {\n const json0 = JSON.stringify(results[0]);\n const json1 = JSON.stringify(results[1]);\n const json2 = JSON.stringify(results[2]);\n if (json0 !== json1 || json1 !== json2) {\n throw new Error('Determinism violation: different results for same input');\n }\n }\n }\n\n async assertImmutability(input: EvaluationInput, rules: ReadonlyArray<Rule>): Promise<void> {\n const inputCopy = JSON.parse(JSON.stringify(input)) as EvaluationInput;\n const rulesCopy = JSON.parse(JSON.stringify(rules)) as Rule[];\n\n if (this.plugin.beforeEvaluate) {\n this.plugin.beforeEvaluate(input, rules);\n }\n\n if (JSON.stringify(input) !== JSON.stringify(inputCopy)) {\n throw new Error('Immutability violation: beforeEvaluate mutated input');\n }\n if (JSON.stringify(rules) !== JSON.stringify(rulesCopy)) {\n throw new Error('Immutability violation: beforeEvaluate mutated rules');\n }\n }\n\n async assertParamsValidation(\n modelName: string,\n invalidParams: unknown[],\n context: PluginContext,\n ): Promise<void> {\n this.plugin.onInit(context);\n\n const model = this.findModel(modelName, context);\n if (!model) {\n throw new Error(`Model \"${modelName}\" not found in plugin`);\n }\n\n for (const params of invalidParams) {\n const result = model.validateParams(params);\n if (result.valid) {\n throw new Error(\n `Expected model \"${modelName}\" to reject params: ${JSON.stringify(params)}`,\n );\n }\n }\n }\n\n async assertNoSideEffects(input: EvaluationInput, rules: ReadonlyArray<Rule>): Promise<void> {\n if (this.plugin.beforeEvaluate) {\n const r1 = this.plugin.beforeEvaluate(input, rules);\n const r2 = this.plugin.beforeEvaluate(input, rules);\n\n if (JSON.stringify(r1) !== JSON.stringify(r2)) {\n throw new Error('Side effects detected: different results between calls');\n }\n }\n }\n\n async runAll(\n input: EvaluationInput,\n rules: ReadonlyArray<Rule>,\n context: PluginContext,\n ): Promise<TestReport> {\n const passed: string[] = [];\n const failed: Array<{ test: string; error: string }> = [];\n\n const tests = [\n { name: 'determinism', fn: () => this.assertDeterminism(input, rules, context) },\n { name: 'immutability', fn: () => this.assertImmutability(input, rules) },\n { name: 'noSideEffects', fn: () => this.assertNoSideEffects(input, rules) },\n ];\n\n for (const t of tests) {\n try {\n await t.fn();\n passed.push(t.name);\n } catch (error) {\n failed.push({\n test: t.name,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return {\n passed,\n failed,\n summary: failed.length === 0 ? 'PASS' : 'FAIL',\n };\n }\n\n private findModel(name: string, context: PluginContext): CalculationModel | undefined {\n try {\n return context.modelRegistry.get(name);\n } catch {\n return undefined;\n }\n }\n}\n","import type { ValidationResult } from '@run-iq/core';\n\nexport interface SchemaFieldDef {\n readonly type: 'number' | 'string' | 'boolean' | 'array' | 'object';\n readonly min?: number | undefined;\n readonly max?: number | undefined;\n readonly enum?: readonly string[] | undefined;\n readonly required?: boolean | undefined;\n}\n\nexport type SchemaDefinition = Record<string, SchemaFieldDef>;\n\nexport class SchemaValidator {\n static validate(params: unknown, schema: SchemaDefinition): ValidationResult {\n const errors: string[] = [];\n\n if (params === null || typeof params !== 'object') {\n return { valid: false, errors: ['params must be a non-null object'] };\n }\n\n const obj = params as Record<string, unknown>;\n\n for (const [key, def] of Object.entries(schema)) {\n const value = obj[key];\n const isRequired = def.required !== false;\n\n if (value === undefined || value === null) {\n if (isRequired) {\n errors.push(`\"${key}\" is required`);\n }\n continue;\n }\n\n if (def.type === 'number') {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n errors.push(`\"${key}\" must be a number`);\n continue;\n }\n if (def.min !== undefined && value < def.min) {\n errors.push(`\"${key}\" must be >= ${def.min}`);\n }\n if (def.max !== undefined && value > def.max) {\n errors.push(`\"${key}\" must be <= ${def.max}`);\n }\n } else if (def.type === 'string') {\n if (typeof value !== 'string') {\n errors.push(`\"${key}\" must be a string`);\n continue;\n }\n if (def.enum && !def.enum.includes(value)) {\n errors.push(`\"${key}\" must be one of: ${def.enum.join(', ')}`);\n }\n } else if (def.type === 'boolean') {\n if (typeof value !== 'boolean') {\n errors.push(`\"${key}\" must be a boolean`);\n }\n } else if (def.type === 'array') {\n if (!Array.isArray(value)) {\n errors.push(`\"${key}\" must be an array`);\n }\n } else if (def.type === 'object') {\n if (typeof value !== 'object' || Array.isArray(value)) {\n errors.push(`\"${key}\" must be an object`);\n }\n }\n }\n\n return errors.length > 0 ? { valid: false, errors } : { valid: true };\n }\n\n static number(value: unknown, opts?: { min?: number; max?: number }): boolean {\n if (typeof value !== 'number' || Number.isNaN(value)) return false;\n if (opts?.min !== undefined && value < opts.min) return false;\n if (opts?.max !== undefined && value > opts.max) return false;\n return true;\n }\n\n static string(value: unknown, opts?: { enum?: readonly string[] }): boolean {\n if (typeof value !== 'string') return false;\n if (opts?.enum && !opts.enum.includes(value)) return false;\n return true;\n }\n\n static positiveNumber(value: unknown): boolean {\n return SchemaValidator.number(value, { min: 0 });\n }\n\n static rate(value: unknown): boolean {\n return SchemaValidator.number(value, { min: 0, max: 1 });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/base/BasePlugin.ts","../src/base/BaseModel.ts","../src/testing/PluginTester.ts","../src/validation/SchemaValidator.ts"],"sourcesContent":["export { BasePlugin } from './base/BasePlugin.js';\nexport { BaseModel } from './base/BaseModel.js';\nexport { PluginTester } from './testing/PluginTester.js';\nexport type { TestReport } from './testing/PluginTester.js';\nexport { SchemaValidator } from './validation/SchemaValidator.js';\nexport type { SchemaDefinition, SchemaFieldDef } from './validation/SchemaValidator.js';\nexport type {\n Rule,\n Expression,\n EvaluationInput,\n EvaluationResult,\n BreakdownItem,\n SkippedRule,\n SkipReason,\n PPEPlugin,\n PluginContext,\n CalculationModel,\n ValidationResult,\n DSLEvaluator,\n ISnapshotAdapter,\n Snapshot,\n EvaluationTrace,\n TraceStep,\n RuleFieldDescriptor,\n InputFieldDescriptor,\n RuleExample,\n PluginDescriptor,\n PluginBundle,\n} from './types/index.js';\n","import type {\n PPEPlugin,\n PluginContext,\n EvaluationInput,\n EvaluationResult,\n Rule,\n CalculationModel,\n PPEError,\n} from '@run-iq/core';\n\nexport abstract class BasePlugin implements PPEPlugin {\n abstract readonly name: string;\n abstract readonly version: string;\n abstract readonly models: CalculationModel[];\n\n onInit(context: PluginContext): void {\n for (const model of this.models) {\n context.modelRegistry.register(model);\n }\n }\n\n beforeEvaluate(input: EvaluationInput, _rules: ReadonlyArray<Rule>): EvaluationInput {\n return input;\n }\n\n afterEvaluate(_input: EvaluationInput, result: EvaluationResult): EvaluationResult {\n return result;\n }\n\n onError(_error: PPEError, _input: EvaluationInput): void {\n // no-op by default\n }\n\n teardown(): void {\n // no-op by default\n }\n}\n","import type { CalculationModel, CalculationOutput, ValidationResult, Rule } from '@run-iq/core';\n\nexport abstract class BaseModel implements CalculationModel {\n abstract readonly name: string;\n abstract readonly version: string;\n\n abstract validateParams(params: unknown): ValidationResult;\n\n abstract calculate(\n input: Record<string, unknown>,\n matchedRule: Readonly<Rule>,\n params: unknown,\n ): number | CalculationOutput;\n}\n","import type {\n PPEPlugin,\n PluginContext,\n EvaluationInput,\n Rule,\n CalculationModel,\n} from '@run-iq/core';\n\nexport interface TestReport {\n readonly passed: string[];\n readonly failed: Array<{ test: string; error: string }>;\n readonly summary: 'PASS' | 'FAIL';\n}\n\nexport class PluginTester {\n private readonly plugin: PPEPlugin;\n\n constructor(plugin: PPEPlugin) {\n this.plugin = plugin;\n }\n\n async assertDeterminism(\n input: EvaluationInput,\n rules: ReadonlyArray<Rule>,\n context: PluginContext,\n ): Promise<void> {\n this.plugin.onInit(context);\n\n const results: EvaluationInput[] = [];\n for (let i = 0; i < 3; i++) {\n if (this.plugin.beforeEvaluate) {\n results.push(this.plugin.beforeEvaluate(input, rules));\n }\n }\n\n if (results.length === 3) {\n const json0 = JSON.stringify(results[0]);\n const json1 = JSON.stringify(results[1]);\n const json2 = JSON.stringify(results[2]);\n if (json0 !== json1 || json1 !== json2) {\n throw new Error('Determinism violation: different results for same input');\n }\n }\n }\n\n async assertImmutability(input: EvaluationInput, rules: ReadonlyArray<Rule>): Promise<void> {\n const inputCopy = JSON.parse(JSON.stringify(input)) as EvaluationInput;\n const rulesCopy = JSON.parse(JSON.stringify(rules)) as Rule[];\n\n if (this.plugin.beforeEvaluate) {\n this.plugin.beforeEvaluate(input, rules);\n }\n\n if (JSON.stringify(input) !== JSON.stringify(inputCopy)) {\n throw new Error('Immutability violation: beforeEvaluate mutated input');\n }\n if (JSON.stringify(rules) !== JSON.stringify(rulesCopy)) {\n throw new Error('Immutability violation: beforeEvaluate mutated rules');\n }\n }\n\n async assertParamsValidation(\n modelName: string,\n invalidParams: unknown[],\n context: PluginContext,\n ): Promise<void> {\n this.plugin.onInit(context);\n\n const model = this.findModel(modelName, context);\n if (!model) {\n throw new Error(`Model \"${modelName}\" not found in plugin`);\n }\n\n for (const params of invalidParams) {\n const result = model.validateParams(params);\n if (result.valid) {\n throw new Error(\n `Expected model \"${modelName}\" to reject params: ${JSON.stringify(params)}`,\n );\n }\n }\n }\n\n async assertNoSideEffects(input: EvaluationInput, rules: ReadonlyArray<Rule>): Promise<void> {\n if (this.plugin.beforeEvaluate) {\n const r1 = this.plugin.beforeEvaluate(input, rules);\n const r2 = this.plugin.beforeEvaluate(input, rules);\n\n if (JSON.stringify(r1) !== JSON.stringify(r2)) {\n throw new Error('Side effects detected: different results between calls');\n }\n }\n }\n\n async runAll(\n input: EvaluationInput,\n rules: ReadonlyArray<Rule>,\n context: PluginContext,\n ): Promise<TestReport> {\n const passed: string[] = [];\n const failed: Array<{ test: string; error: string }> = [];\n\n const tests = [\n { name: 'determinism', fn: () => this.assertDeterminism(input, rules, context) },\n { name: 'immutability', fn: () => this.assertImmutability(input, rules) },\n { name: 'noSideEffects', fn: () => this.assertNoSideEffects(input, rules) },\n ];\n\n for (const t of tests) {\n try {\n await t.fn();\n passed.push(t.name);\n } catch (error) {\n failed.push({\n test: t.name,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return {\n passed,\n failed,\n summary: failed.length === 0 ? 'PASS' : 'FAIL',\n };\n }\n\n private findModel(name: string, context: PluginContext): CalculationModel | undefined {\n try {\n return context.modelRegistry.get(name);\n } catch {\n return undefined;\n }\n }\n}\n","import type { ValidationResult } from '@run-iq/core';\n\nexport interface SchemaFieldDef {\n readonly type: 'number' | 'string' | 'boolean' | 'array' | 'object';\n readonly min?: number | undefined;\n readonly max?: number | undefined;\n readonly enum?: readonly string[] | undefined;\n readonly required?: boolean | undefined;\n}\n\nexport type SchemaDefinition = Record<string, SchemaFieldDef>;\n\nexport class SchemaValidator {\n static validate(params: unknown, schema: SchemaDefinition): ValidationResult {\n const errors: string[] = [];\n\n if (params === null || typeof params !== 'object') {\n return { valid: false, errors: ['params must be a non-null object'] };\n }\n\n const obj = params as Record<string, unknown>;\n\n for (const [key, def] of Object.entries(schema)) {\n const value = obj[key];\n const isRequired = def.required !== false;\n\n if (value === undefined || value === null) {\n if (isRequired) {\n errors.push(`\"${key}\" is required`);\n }\n continue;\n }\n\n if (def.type === 'number') {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n errors.push(`\"${key}\" must be a number`);\n continue;\n }\n if (def.min !== undefined && value < def.min) {\n errors.push(`\"${key}\" must be >= ${def.min}`);\n }\n if (def.max !== undefined && value > def.max) {\n errors.push(`\"${key}\" must be <= ${def.max}`);\n }\n } else if (def.type === 'string') {\n if (typeof value !== 'string') {\n errors.push(`\"${key}\" must be a string`);\n continue;\n }\n if (def.enum && !def.enum.includes(value)) {\n errors.push(`\"${key}\" must be one of: ${def.enum.join(', ')}`);\n }\n } else if (def.type === 'boolean') {\n if (typeof value !== 'boolean') {\n errors.push(`\"${key}\" must be a boolean`);\n }\n } else if (def.type === 'array') {\n if (!Array.isArray(value)) {\n errors.push(`\"${key}\" must be an array`);\n }\n } else if (def.type === 'object') {\n if (typeof value !== 'object' || Array.isArray(value)) {\n errors.push(`\"${key}\" must be an object`);\n }\n }\n }\n\n return errors.length > 0 ? { valid: false, errors } : { valid: true };\n }\n\n static number(value: unknown, opts?: { min?: number; max?: number }): boolean {\n if (typeof value !== 'number' || Number.isNaN(value)) return false;\n if (opts?.min !== undefined && value < opts.min) return false;\n if (opts?.max !== undefined && value > opts.max) return false;\n return true;\n }\n\n static string(value: unknown, opts?: { enum?: readonly string[] }): boolean {\n if (typeof value !== 'string') return false;\n if (opts?.enum && !opts.enum.includes(value)) return false;\n return true;\n }\n\n static positiveNumber(value: unknown): boolean {\n return SchemaValidator.number(value, { min: 0 });\n }\n\n static rate(value: unknown): boolean {\n return SchemaValidator.number(value, { min: 0, max: 1 });\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { PPEPlugin, CalculationModel, PluginContext, EvaluationInput, Rule, EvaluationResult, PPEError, ValidationResult } from '@run-iq/core';
1
+ import { PPEPlugin, CalculationModel, PluginContext, EvaluationInput, Rule, EvaluationResult, PPEError, ValidationResult, CalculationOutput, DSLEvaluator } from '@run-iq/core';
2
2
  export { BreakdownItem, CalculationModel, DSLEvaluator, EvaluationInput, EvaluationResult, EvaluationTrace, Expression, ISnapshotAdapter, PPEPlugin, PluginContext, Rule, SkipReason, SkippedRule, Snapshot, TraceStep, ValidationResult } from '@run-iq/core';
3
3
 
4
4
  declare abstract class BasePlugin implements PPEPlugin {
@@ -16,7 +16,7 @@ declare abstract class BaseModel implements CalculationModel {
16
16
  abstract readonly name: string;
17
17
  abstract readonly version: string;
18
18
  abstract validateParams(params: unknown): ValidationResult;
19
- abstract calculate(input: Record<string, unknown>, matchedRule: Readonly<Rule>, params: unknown): number;
19
+ abstract calculate(input: Record<string, unknown>, matchedRule: Readonly<Rule>, params: unknown): number | CalculationOutput;
20
20
  }
21
21
 
22
22
  interface TestReport {
@@ -59,4 +59,81 @@ declare class SchemaValidator {
59
59
  static rate(value: unknown): boolean;
60
60
  }
61
61
 
62
- export { BaseModel, BasePlugin, PluginTester, type SchemaDefinition, type SchemaFieldDef, SchemaValidator, type TestReport };
62
+ /**
63
+ * Describes a single field that a plugin adds to the Rule object.
64
+ * Used by tooling (MCP server, CLI, Playground) to dynamically build
65
+ * input schemas and validate rule structures.
66
+ */
67
+ interface RuleFieldDescriptor {
68
+ readonly name: string;
69
+ readonly type: 'string' | 'number' | 'boolean';
70
+ readonly required: boolean;
71
+ readonly description: string;
72
+ readonly enum?: readonly string[];
73
+ }
74
+ /**
75
+ * Describes an input data variable that a plugin expects in `input.data`.
76
+ * Used by tooling to document available variables for conditions and models.
77
+ */
78
+ interface InputFieldDescriptor {
79
+ readonly name: string;
80
+ readonly type: 'string' | 'number' | 'boolean' | 'object' | 'array';
81
+ readonly description: string;
82
+ readonly examples?: readonly unknown[];
83
+ }
84
+ /**
85
+ * A complete rule example with optional input data.
86
+ * Used by tooling to show concrete usage patterns to LLMs and users.
87
+ */
88
+ interface RuleExample {
89
+ readonly title: string;
90
+ readonly description: string;
91
+ readonly rule: Record<string, unknown>;
92
+ readonly input?: Record<string, unknown>;
93
+ }
94
+ /**
95
+ * Self-describing metadata that a plugin provides to tooling layers.
96
+ * This is the contract between a plugin and any consumer that needs
97
+ * to understand the plugin's domain (MCP server, CLI, Playground, etc.).
98
+ *
99
+ * The plugin knows its domain best — it declares what fields it adds
100
+ * to rules, what input variables it expects, what examples illustrate
101
+ * its usage, and what guidelines help an LLM produce correct rules.
102
+ */
103
+ interface PluginDescriptor {
104
+ /** Plugin package name (e.g. "@run-iq/plugin-fiscal") */
105
+ readonly name: string;
106
+ /** Plugin version (e.g. "0.1.0") */
107
+ readonly version: string;
108
+ /** Human-readable description of the plugin's domain and capabilities */
109
+ readonly description: string;
110
+ /** Short domain label used for prompt naming and context (e.g. "fiscal", "social", "payroll") */
111
+ readonly domainLabel: string;
112
+ /** Fields this plugin adds to the Rule object (e.g. jurisdiction, scope, country) */
113
+ readonly ruleExtensions: readonly RuleFieldDescriptor[];
114
+ /** Input data variables the plugin's models expect in input.data */
115
+ readonly inputFields: readonly InputFieldDescriptor[];
116
+ /** Concrete rule examples demonstrating the plugin's models and fields */
117
+ readonly examples: readonly RuleExample[];
118
+ /**
119
+ * Domain-specific guidelines for LLMs and tooling.
120
+ * These help an AI understand how to create correct rules, analyze
121
+ * domain-specific text, and provide expert advice in this domain.
122
+ *
123
+ * Guidelines should be universal (not tied to a specific country/region)
124
+ * and cover: model selection, field usage, best practices, common patterns.
125
+ */
126
+ readonly promptGuidelines: readonly string[];
127
+ }
128
+ /**
129
+ * A complete plugin package ready for registration.
130
+ * Ties together the runtime plugin, its self-describing metadata,
131
+ * and optional DSL evaluators it depends on.
132
+ */
133
+ interface PluginBundle {
134
+ readonly plugin: PPEPlugin;
135
+ readonly descriptor: PluginDescriptor;
136
+ readonly dsls?: readonly DSLEvaluator[];
137
+ }
138
+
139
+ export { BaseModel, BasePlugin, type InputFieldDescriptor, type PluginBundle, type PluginDescriptor, PluginTester, type RuleExample, type RuleFieldDescriptor, type SchemaDefinition, type SchemaFieldDef, SchemaValidator, type TestReport };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PPEPlugin, CalculationModel, PluginContext, EvaluationInput, Rule, EvaluationResult, PPEError, ValidationResult } from '@run-iq/core';
1
+ import { PPEPlugin, CalculationModel, PluginContext, EvaluationInput, Rule, EvaluationResult, PPEError, ValidationResult, CalculationOutput, DSLEvaluator } from '@run-iq/core';
2
2
  export { BreakdownItem, CalculationModel, DSLEvaluator, EvaluationInput, EvaluationResult, EvaluationTrace, Expression, ISnapshotAdapter, PPEPlugin, PluginContext, Rule, SkipReason, SkippedRule, Snapshot, TraceStep, ValidationResult } from '@run-iq/core';
3
3
 
4
4
  declare abstract class BasePlugin implements PPEPlugin {
@@ -16,7 +16,7 @@ declare abstract class BaseModel implements CalculationModel {
16
16
  abstract readonly name: string;
17
17
  abstract readonly version: string;
18
18
  abstract validateParams(params: unknown): ValidationResult;
19
- abstract calculate(input: Record<string, unknown>, matchedRule: Readonly<Rule>, params: unknown): number;
19
+ abstract calculate(input: Record<string, unknown>, matchedRule: Readonly<Rule>, params: unknown): number | CalculationOutput;
20
20
  }
21
21
 
22
22
  interface TestReport {
@@ -59,4 +59,81 @@ declare class SchemaValidator {
59
59
  static rate(value: unknown): boolean;
60
60
  }
61
61
 
62
- export { BaseModel, BasePlugin, PluginTester, type SchemaDefinition, type SchemaFieldDef, SchemaValidator, type TestReport };
62
+ /**
63
+ * Describes a single field that a plugin adds to the Rule object.
64
+ * Used by tooling (MCP server, CLI, Playground) to dynamically build
65
+ * input schemas and validate rule structures.
66
+ */
67
+ interface RuleFieldDescriptor {
68
+ readonly name: string;
69
+ readonly type: 'string' | 'number' | 'boolean';
70
+ readonly required: boolean;
71
+ readonly description: string;
72
+ readonly enum?: readonly string[];
73
+ }
74
+ /**
75
+ * Describes an input data variable that a plugin expects in `input.data`.
76
+ * Used by tooling to document available variables for conditions and models.
77
+ */
78
+ interface InputFieldDescriptor {
79
+ readonly name: string;
80
+ readonly type: 'string' | 'number' | 'boolean' | 'object' | 'array';
81
+ readonly description: string;
82
+ readonly examples?: readonly unknown[];
83
+ }
84
+ /**
85
+ * A complete rule example with optional input data.
86
+ * Used by tooling to show concrete usage patterns to LLMs and users.
87
+ */
88
+ interface RuleExample {
89
+ readonly title: string;
90
+ readonly description: string;
91
+ readonly rule: Record<string, unknown>;
92
+ readonly input?: Record<string, unknown>;
93
+ }
94
+ /**
95
+ * Self-describing metadata that a plugin provides to tooling layers.
96
+ * This is the contract between a plugin and any consumer that needs
97
+ * to understand the plugin's domain (MCP server, CLI, Playground, etc.).
98
+ *
99
+ * The plugin knows its domain best — it declares what fields it adds
100
+ * to rules, what input variables it expects, what examples illustrate
101
+ * its usage, and what guidelines help an LLM produce correct rules.
102
+ */
103
+ interface PluginDescriptor {
104
+ /** Plugin package name (e.g. "@run-iq/plugin-fiscal") */
105
+ readonly name: string;
106
+ /** Plugin version (e.g. "0.1.0") */
107
+ readonly version: string;
108
+ /** Human-readable description of the plugin's domain and capabilities */
109
+ readonly description: string;
110
+ /** Short domain label used for prompt naming and context (e.g. "fiscal", "social", "payroll") */
111
+ readonly domainLabel: string;
112
+ /** Fields this plugin adds to the Rule object (e.g. jurisdiction, scope, country) */
113
+ readonly ruleExtensions: readonly RuleFieldDescriptor[];
114
+ /** Input data variables the plugin's models expect in input.data */
115
+ readonly inputFields: readonly InputFieldDescriptor[];
116
+ /** Concrete rule examples demonstrating the plugin's models and fields */
117
+ readonly examples: readonly RuleExample[];
118
+ /**
119
+ * Domain-specific guidelines for LLMs and tooling.
120
+ * These help an AI understand how to create correct rules, analyze
121
+ * domain-specific text, and provide expert advice in this domain.
122
+ *
123
+ * Guidelines should be universal (not tied to a specific country/region)
124
+ * and cover: model selection, field usage, best practices, common patterns.
125
+ */
126
+ readonly promptGuidelines: readonly string[];
127
+ }
128
+ /**
129
+ * A complete plugin package ready for registration.
130
+ * Ties together the runtime plugin, its self-describing metadata,
131
+ * and optional DSL evaluators it depends on.
132
+ */
133
+ interface PluginBundle {
134
+ readonly plugin: PPEPlugin;
135
+ readonly descriptor: PluginDescriptor;
136
+ readonly dsls?: readonly DSLEvaluator[];
137
+ }
138
+
139
+ export { BaseModel, BasePlugin, type InputFieldDescriptor, type PluginBundle, type PluginDescriptor, PluginTester, type RuleExample, type RuleFieldDescriptor, type SchemaDefinition, type SchemaFieldDef, SchemaValidator, type TestReport };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/base/BasePlugin.ts","../src/base/BaseModel.ts","../src/testing/PluginTester.ts","../src/validation/SchemaValidator.ts"],"sourcesContent":["import type {\n PPEPlugin,\n PluginContext,\n EvaluationInput,\n EvaluationResult,\n Rule,\n CalculationModel,\n PPEError,\n} from '@run-iq/core';\n\nexport abstract class BasePlugin implements PPEPlugin {\n abstract readonly name: string;\n abstract readonly version: string;\n abstract readonly models: CalculationModel[];\n\n onInit(context: PluginContext): void {\n for (const model of this.models) {\n context.modelRegistry.register(model);\n }\n }\n\n beforeEvaluate(input: EvaluationInput, _rules: ReadonlyArray<Rule>): EvaluationInput {\n return input;\n }\n\n afterEvaluate(_input: EvaluationInput, result: EvaluationResult): EvaluationResult {\n return result;\n }\n\n onError(_error: PPEError, _input: EvaluationInput): void {\n // no-op by default\n }\n\n teardown(): void {\n // no-op by default\n }\n}\n","import type { CalculationModel, ValidationResult, Rule } from '@run-iq/core';\n\nexport abstract class BaseModel implements CalculationModel {\n abstract readonly name: string;\n abstract readonly version: string;\n\n abstract validateParams(params: unknown): ValidationResult;\n\n abstract calculate(\n input: Record<string, unknown>,\n matchedRule: Readonly<Rule>,\n params: unknown,\n ): number;\n}\n","import type {\n PPEPlugin,\n PluginContext,\n EvaluationInput,\n Rule,\n CalculationModel,\n} from '@run-iq/core';\n\nexport interface TestReport {\n readonly passed: string[];\n readonly failed: Array<{ test: string; error: string }>;\n readonly summary: 'PASS' | 'FAIL';\n}\n\nexport class PluginTester {\n private readonly plugin: PPEPlugin;\n\n constructor(plugin: PPEPlugin) {\n this.plugin = plugin;\n }\n\n async assertDeterminism(\n input: EvaluationInput,\n rules: ReadonlyArray<Rule>,\n context: PluginContext,\n ): Promise<void> {\n this.plugin.onInit(context);\n\n const results: EvaluationInput[] = [];\n for (let i = 0; i < 3; i++) {\n if (this.plugin.beforeEvaluate) {\n results.push(this.plugin.beforeEvaluate(input, rules));\n }\n }\n\n if (results.length === 3) {\n const json0 = JSON.stringify(results[0]);\n const json1 = JSON.stringify(results[1]);\n const json2 = JSON.stringify(results[2]);\n if (json0 !== json1 || json1 !== json2) {\n throw new Error('Determinism violation: different results for same input');\n }\n }\n }\n\n async assertImmutability(input: EvaluationInput, rules: ReadonlyArray<Rule>): Promise<void> {\n const inputCopy = JSON.parse(JSON.stringify(input)) as EvaluationInput;\n const rulesCopy = JSON.parse(JSON.stringify(rules)) as Rule[];\n\n if (this.plugin.beforeEvaluate) {\n this.plugin.beforeEvaluate(input, rules);\n }\n\n if (JSON.stringify(input) !== JSON.stringify(inputCopy)) {\n throw new Error('Immutability violation: beforeEvaluate mutated input');\n }\n if (JSON.stringify(rules) !== JSON.stringify(rulesCopy)) {\n throw new Error('Immutability violation: beforeEvaluate mutated rules');\n }\n }\n\n async assertParamsValidation(\n modelName: string,\n invalidParams: unknown[],\n context: PluginContext,\n ): Promise<void> {\n this.plugin.onInit(context);\n\n const model = this.findModel(modelName, context);\n if (!model) {\n throw new Error(`Model \"${modelName}\" not found in plugin`);\n }\n\n for (const params of invalidParams) {\n const result = model.validateParams(params);\n if (result.valid) {\n throw new Error(\n `Expected model \"${modelName}\" to reject params: ${JSON.stringify(params)}`,\n );\n }\n }\n }\n\n async assertNoSideEffects(input: EvaluationInput, rules: ReadonlyArray<Rule>): Promise<void> {\n if (this.plugin.beforeEvaluate) {\n const r1 = this.plugin.beforeEvaluate(input, rules);\n const r2 = this.plugin.beforeEvaluate(input, rules);\n\n if (JSON.stringify(r1) !== JSON.stringify(r2)) {\n throw new Error('Side effects detected: different results between calls');\n }\n }\n }\n\n async runAll(\n input: EvaluationInput,\n rules: ReadonlyArray<Rule>,\n context: PluginContext,\n ): Promise<TestReport> {\n const passed: string[] = [];\n const failed: Array<{ test: string; error: string }> = [];\n\n const tests = [\n { name: 'determinism', fn: () => this.assertDeterminism(input, rules, context) },\n { name: 'immutability', fn: () => this.assertImmutability(input, rules) },\n { name: 'noSideEffects', fn: () => this.assertNoSideEffects(input, rules) },\n ];\n\n for (const t of tests) {\n try {\n await t.fn();\n passed.push(t.name);\n } catch (error) {\n failed.push({\n test: t.name,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return {\n passed,\n failed,\n summary: failed.length === 0 ? 'PASS' : 'FAIL',\n };\n }\n\n private findModel(name: string, context: PluginContext): CalculationModel | undefined {\n try {\n return context.modelRegistry.get(name);\n } catch {\n return undefined;\n }\n }\n}\n","import type { ValidationResult } from '@run-iq/core';\n\nexport interface SchemaFieldDef {\n readonly type: 'number' | 'string' | 'boolean' | 'array' | 'object';\n readonly min?: number | undefined;\n readonly max?: number | undefined;\n readonly enum?: readonly string[] | undefined;\n readonly required?: boolean | undefined;\n}\n\nexport type SchemaDefinition = Record<string, SchemaFieldDef>;\n\nexport class SchemaValidator {\n static validate(params: unknown, schema: SchemaDefinition): ValidationResult {\n const errors: string[] = [];\n\n if (params === null || typeof params !== 'object') {\n return { valid: false, errors: ['params must be a non-null object'] };\n }\n\n const obj = params as Record<string, unknown>;\n\n for (const [key, def] of Object.entries(schema)) {\n const value = obj[key];\n const isRequired = def.required !== false;\n\n if (value === undefined || value === null) {\n if (isRequired) {\n errors.push(`\"${key}\" is required`);\n }\n continue;\n }\n\n if (def.type === 'number') {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n errors.push(`\"${key}\" must be a number`);\n continue;\n }\n if (def.min !== undefined && value < def.min) {\n errors.push(`\"${key}\" must be >= ${def.min}`);\n }\n if (def.max !== undefined && value > def.max) {\n errors.push(`\"${key}\" must be <= ${def.max}`);\n }\n } else if (def.type === 'string') {\n if (typeof value !== 'string') {\n errors.push(`\"${key}\" must be a string`);\n continue;\n }\n if (def.enum && !def.enum.includes(value)) {\n errors.push(`\"${key}\" must be one of: ${def.enum.join(', ')}`);\n }\n } else if (def.type === 'boolean') {\n if (typeof value !== 'boolean') {\n errors.push(`\"${key}\" must be a boolean`);\n }\n } else if (def.type === 'array') {\n if (!Array.isArray(value)) {\n errors.push(`\"${key}\" must be an array`);\n }\n } else if (def.type === 'object') {\n if (typeof value !== 'object' || Array.isArray(value)) {\n errors.push(`\"${key}\" must be an object`);\n }\n }\n }\n\n return errors.length > 0 ? { valid: false, errors } : { valid: true };\n }\n\n static number(value: unknown, opts?: { min?: number; max?: number }): boolean {\n if (typeof value !== 'number' || Number.isNaN(value)) return false;\n if (opts?.min !== undefined && value < opts.min) return false;\n if (opts?.max !== undefined && value > opts.max) return false;\n return true;\n }\n\n static string(value: unknown, opts?: { enum?: readonly string[] }): boolean {\n if (typeof value !== 'string') return false;\n if (opts?.enum && !opts.enum.includes(value)) return false;\n return true;\n }\n\n static positiveNumber(value: unknown): boolean {\n return SchemaValidator.number(value, { min: 0 });\n }\n\n static rate(value: unknown): boolean {\n return SchemaValidator.number(value, { min: 0, max: 1 });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/base/BasePlugin.ts","../src/base/BaseModel.ts","../src/testing/PluginTester.ts","../src/validation/SchemaValidator.ts"],"sourcesContent":["import type {\n PPEPlugin,\n PluginContext,\n EvaluationInput,\n EvaluationResult,\n Rule,\n CalculationModel,\n PPEError,\n} from '@run-iq/core';\n\nexport abstract class BasePlugin implements PPEPlugin {\n abstract readonly name: string;\n abstract readonly version: string;\n abstract readonly models: CalculationModel[];\n\n onInit(context: PluginContext): void {\n for (const model of this.models) {\n context.modelRegistry.register(model);\n }\n }\n\n beforeEvaluate(input: EvaluationInput, _rules: ReadonlyArray<Rule>): EvaluationInput {\n return input;\n }\n\n afterEvaluate(_input: EvaluationInput, result: EvaluationResult): EvaluationResult {\n return result;\n }\n\n onError(_error: PPEError, _input: EvaluationInput): void {\n // no-op by default\n }\n\n teardown(): void {\n // no-op by default\n }\n}\n","import type { CalculationModel, CalculationOutput, ValidationResult, Rule } from '@run-iq/core';\n\nexport abstract class BaseModel implements CalculationModel {\n abstract readonly name: string;\n abstract readonly version: string;\n\n abstract validateParams(params: unknown): ValidationResult;\n\n abstract calculate(\n input: Record<string, unknown>,\n matchedRule: Readonly<Rule>,\n params: unknown,\n ): number | CalculationOutput;\n}\n","import type {\n PPEPlugin,\n PluginContext,\n EvaluationInput,\n Rule,\n CalculationModel,\n} from '@run-iq/core';\n\nexport interface TestReport {\n readonly passed: string[];\n readonly failed: Array<{ test: string; error: string }>;\n readonly summary: 'PASS' | 'FAIL';\n}\n\nexport class PluginTester {\n private readonly plugin: PPEPlugin;\n\n constructor(plugin: PPEPlugin) {\n this.plugin = plugin;\n }\n\n async assertDeterminism(\n input: EvaluationInput,\n rules: ReadonlyArray<Rule>,\n context: PluginContext,\n ): Promise<void> {\n this.plugin.onInit(context);\n\n const results: EvaluationInput[] = [];\n for (let i = 0; i < 3; i++) {\n if (this.plugin.beforeEvaluate) {\n results.push(this.plugin.beforeEvaluate(input, rules));\n }\n }\n\n if (results.length === 3) {\n const json0 = JSON.stringify(results[0]);\n const json1 = JSON.stringify(results[1]);\n const json2 = JSON.stringify(results[2]);\n if (json0 !== json1 || json1 !== json2) {\n throw new Error('Determinism violation: different results for same input');\n }\n }\n }\n\n async assertImmutability(input: EvaluationInput, rules: ReadonlyArray<Rule>): Promise<void> {\n const inputCopy = JSON.parse(JSON.stringify(input)) as EvaluationInput;\n const rulesCopy = JSON.parse(JSON.stringify(rules)) as Rule[];\n\n if (this.plugin.beforeEvaluate) {\n this.plugin.beforeEvaluate(input, rules);\n }\n\n if (JSON.stringify(input) !== JSON.stringify(inputCopy)) {\n throw new Error('Immutability violation: beforeEvaluate mutated input');\n }\n if (JSON.stringify(rules) !== JSON.stringify(rulesCopy)) {\n throw new Error('Immutability violation: beforeEvaluate mutated rules');\n }\n }\n\n async assertParamsValidation(\n modelName: string,\n invalidParams: unknown[],\n context: PluginContext,\n ): Promise<void> {\n this.plugin.onInit(context);\n\n const model = this.findModel(modelName, context);\n if (!model) {\n throw new Error(`Model \"${modelName}\" not found in plugin`);\n }\n\n for (const params of invalidParams) {\n const result = model.validateParams(params);\n if (result.valid) {\n throw new Error(\n `Expected model \"${modelName}\" to reject params: ${JSON.stringify(params)}`,\n );\n }\n }\n }\n\n async assertNoSideEffects(input: EvaluationInput, rules: ReadonlyArray<Rule>): Promise<void> {\n if (this.plugin.beforeEvaluate) {\n const r1 = this.plugin.beforeEvaluate(input, rules);\n const r2 = this.plugin.beforeEvaluate(input, rules);\n\n if (JSON.stringify(r1) !== JSON.stringify(r2)) {\n throw new Error('Side effects detected: different results between calls');\n }\n }\n }\n\n async runAll(\n input: EvaluationInput,\n rules: ReadonlyArray<Rule>,\n context: PluginContext,\n ): Promise<TestReport> {\n const passed: string[] = [];\n const failed: Array<{ test: string; error: string }> = [];\n\n const tests = [\n { name: 'determinism', fn: () => this.assertDeterminism(input, rules, context) },\n { name: 'immutability', fn: () => this.assertImmutability(input, rules) },\n { name: 'noSideEffects', fn: () => this.assertNoSideEffects(input, rules) },\n ];\n\n for (const t of tests) {\n try {\n await t.fn();\n passed.push(t.name);\n } catch (error) {\n failed.push({\n test: t.name,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return {\n passed,\n failed,\n summary: failed.length === 0 ? 'PASS' : 'FAIL',\n };\n }\n\n private findModel(name: string, context: PluginContext): CalculationModel | undefined {\n try {\n return context.modelRegistry.get(name);\n } catch {\n return undefined;\n }\n }\n}\n","import type { ValidationResult } from '@run-iq/core';\n\nexport interface SchemaFieldDef {\n readonly type: 'number' | 'string' | 'boolean' | 'array' | 'object';\n readonly min?: number | undefined;\n readonly max?: number | undefined;\n readonly enum?: readonly string[] | undefined;\n readonly required?: boolean | undefined;\n}\n\nexport type SchemaDefinition = Record<string, SchemaFieldDef>;\n\nexport class SchemaValidator {\n static validate(params: unknown, schema: SchemaDefinition): ValidationResult {\n const errors: string[] = [];\n\n if (params === null || typeof params !== 'object') {\n return { valid: false, errors: ['params must be a non-null object'] };\n }\n\n const obj = params as Record<string, unknown>;\n\n for (const [key, def] of Object.entries(schema)) {\n const value = obj[key];\n const isRequired = def.required !== false;\n\n if (value === undefined || value === null) {\n if (isRequired) {\n errors.push(`\"${key}\" is required`);\n }\n continue;\n }\n\n if (def.type === 'number') {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n errors.push(`\"${key}\" must be a number`);\n continue;\n }\n if (def.min !== undefined && value < def.min) {\n errors.push(`\"${key}\" must be >= ${def.min}`);\n }\n if (def.max !== undefined && value > def.max) {\n errors.push(`\"${key}\" must be <= ${def.max}`);\n }\n } else if (def.type === 'string') {\n if (typeof value !== 'string') {\n errors.push(`\"${key}\" must be a string`);\n continue;\n }\n if (def.enum && !def.enum.includes(value)) {\n errors.push(`\"${key}\" must be one of: ${def.enum.join(', ')}`);\n }\n } else if (def.type === 'boolean') {\n if (typeof value !== 'boolean') {\n errors.push(`\"${key}\" must be a boolean`);\n }\n } else if (def.type === 'array') {\n if (!Array.isArray(value)) {\n errors.push(`\"${key}\" must be an array`);\n }\n } else if (def.type === 'object') {\n if (typeof value !== 'object' || Array.isArray(value)) {\n errors.push(`\"${key}\" must be an object`);\n }\n }\n }\n\n return errors.length > 0 ? { valid: false, errors } : { valid: true };\n }\n\n static number(value: unknown, opts?: { min?: number; max?: number }): boolean {\n if (typeof value !== 'number' || Number.isNaN(value)) return false;\n if (opts?.min !== undefined && value < opts.min) return false;\n if (opts?.max !== undefined && value > opts.max) return false;\n return true;\n }\n\n static string(value: unknown, opts?: { enum?: readonly string[] }): boolean {\n if (typeof value !== 'string') return false;\n if (opts?.enum && !opts.enum.includes(value)) return false;\n return true;\n }\n\n static positiveNumber(value: unknown): boolean {\n return SchemaValidator.number(value, { min: 0 });\n }\n\n static rate(value: unknown): boolean {\n return SchemaValidator.number(value, { min: 0, max: 1 });\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@run-iq/plugin-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Plugin SDK for PPE — BasePlugin, BaseModel, PluginTester",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -18,7 +18,9 @@
18
18
  }
19
19
  }
20
20
  },
21
- "files": ["dist"],
21
+ "files": [
22
+ "dist"
23
+ ],
22
24
  "scripts": {
23
25
  "build": "tsup",
24
26
  "test": "vitest run",
@@ -27,7 +29,7 @@
27
29
  "lint:fix": "eslint src/ tests/ --fix && prettier --write src/ tests/"
28
30
  },
29
31
  "peerDependencies": {
30
- "@run-iq/core": "^0.1.0"
32
+ "@run-iq/core": "^0.1.3"
31
33
  },
32
34
  "devDependencies": {
33
35
  "@types/node": "^20.11.0",