@siduri-x/core 1.0.8 → 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.
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export * from './action-executor';
22
22
  export * from './experience-emitter';
23
23
  export * from './response-envelope';
24
24
  export * from './session-history';
25
+ export * from './schema-validator';
25
26
  import { EvidenceRecord } from './evidence';
26
27
  import { ActionIntent } from './action';
27
28
  import { RequestContext } from './context';
package/dist/index.js CHANGED
@@ -39,5 +39,6 @@ __exportStar(require("./action-executor"), exports);
39
39
  __exportStar(require("./experience-emitter"), exports);
40
40
  __exportStar(require("./response-envelope"), exports);
41
41
  __exportStar(require("./session-history"), exports);
42
+ __exportStar(require("./schema-validator"), exports);
42
43
  // Mouth (Communication & Output Delivery)
43
44
  __exportStar(require("./mouth-types"), exports);
@@ -66,4 +66,30 @@ describe('SiduriRuntime Facade Methods & Delegation', () => {
66
66
  await runtime.resetMemory();
67
67
  expect(mockMemory.resetMemory).toHaveBeenCalled();
68
68
  });
69
+ test('configures SqliteActionStore when actionStore is sqlite', () => {
70
+ const runtime = new runtime_1.SiduriRuntime('comp-sqlite', {
71
+ id: 'comp-sqlite',
72
+ name: 'Sqlite Test',
73
+ actionStore: 'sqlite',
74
+ });
75
+ expect(runtime.actionPolicy.getStore()).toBeDefined();
76
+ // Verify it is an instance of SqliteActionStore
77
+ expect(runtime.actionPolicy.getStore().constructor.name).toBe('SqliteActionStore');
78
+ });
79
+ test('accepts custom actionStore via RuntimeOrgans', () => {
80
+ const customStore = {
81
+ recordExecution: jest.fn(),
82
+ getExecution: jest.fn(),
83
+ updateExecution: jest.fn(),
84
+ recordApproval: jest.fn(),
85
+ getApproval: jest.fn(),
86
+ recordAudit: jest.fn(),
87
+ getAuditLog: jest.fn(),
88
+ verifyAuditChain: jest.fn(),
89
+ };
90
+ const runtime = new runtime_1.SiduriRuntime('comp-custom', { id: 'comp-custom', name: 'Custom' }, {
91
+ actionStore: customStore,
92
+ });
93
+ expect(runtime.actionPolicy.getStore()).toBe(customStore);
94
+ });
69
95
  });
package/dist/runtime.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BrainOrgan, MemoryOrgan, VoiceOrgan, KnowledgeOrgan, VisionOrgan, BehaviorOrgan, BodyOrgan, HandsOrgan, EarOrgan, ObservationOrgan, ObservationResult, Observation, Message, RequestContext, ActionPolicyEngine, ResponseGatingEngine, StageResponseOptions, ApproveResponseOptions, RejectResponseOptions, ExperienceDispatcher, ExperienceAdapter, OrganConfig, Claim, BehaviorDirective, StagedResponsePlan, ResponseGateEvaluation, EvidenceRecord, MouthOrgan, MouthUtterance, MouthMedium, FormattedMouthOutput, MouthStreamChunk, MouthChannel } from './index';
1
+ import { BrainOrgan, MemoryOrgan, VoiceOrgan, KnowledgeOrgan, VisionOrgan, BehaviorOrgan, BodyOrgan, HandsOrgan, EarOrgan, ObservationOrgan, ObservationResult, Observation, Message, RequestContext, ActionPolicyEngine, ActionStore, ResponseGatingEngine, StageResponseOptions, ApproveResponseOptions, RejectResponseOptions, ExperienceDispatcher, ExperienceAdapter, OrganConfig, Claim, BehaviorDirective, StagedResponsePlan, ResponseGateEvaluation, EvidenceRecord, MouthOrgan, MouthUtterance, MouthMedium, FormattedMouthOutput, MouthStreamChunk, MouthChannel } from './index';
2
2
  export interface SiduriRuntimeConfig {
3
3
  name: string;
4
4
  brain?: OrganConfig | Record<string, unknown>;
@@ -13,6 +13,11 @@ export interface SiduriRuntimeConfig {
13
13
  observation?: OrganConfig | Record<string, unknown>;
14
14
  mouth?: OrganConfig | Record<string, unknown>;
15
15
  actionPolicy?: Record<string, unknown>;
16
+ actionStore?: 'in-memory' | 'sqlite' | {
17
+ type: 'sqlite' | 'in-memory';
18
+ dbPath?: string;
19
+ };
20
+ actionStorePath?: string;
16
21
  [key: string]: unknown;
17
22
  }
18
23
  export interface RuntimeOrgans {
@@ -27,6 +32,7 @@ export interface RuntimeOrgans {
27
32
  ear?: EarOrgan;
28
33
  observation?: ObservationOrgan;
29
34
  mouth?: MouthOrgan;
35
+ actionStore?: ActionStore;
30
36
  actionPolicy?: ActionPolicyEngine;
31
37
  }
32
38
  export interface CompanionPerception {
package/dist/runtime.js CHANGED
@@ -57,7 +57,17 @@ class SiduriRuntime {
57
57
  this.observation = organs.observation;
58
58
  this.mouth = organs.mouth;
59
59
  this.gating = new index_1.ResponseGatingEngine();
60
- this.actionPolicy = organs.actionPolicy || new index_1.ActionPolicyEngine();
60
+ let actionStore = organs.actionStore;
61
+ if (!actionStore) {
62
+ const storeOpt = config.actionStore;
63
+ const storePath = config.actionStorePath || (typeof storeOpt === 'object' ? storeOpt.dbPath : undefined);
64
+ if (storeOpt === 'sqlite' || (typeof storeOpt === 'object' && storeOpt.type === 'sqlite') || storePath) {
65
+ actionStore = new index_1.SqliteActionStore({ dbPath: storePath });
66
+ }
67
+ }
68
+ this.actionPolicy = organs.actionPolicy || new index_1.ActionPolicyEngine({
69
+ store: actionStore,
70
+ });
61
71
  this.dispatcher = new index_1.ExperienceDispatcher();
62
72
  if (this.voice && typeof this.voice.handleEvent === 'function') {
63
73
  this.dispatcher.registerAdapter(this.voice);
@@ -0,0 +1,9 @@
1
+ export declare class ConfigValidationError extends Error {
2
+ errors: string[];
3
+ constructor(errors: string[]);
4
+ }
5
+ /**
6
+ * Validates a companion configuration object against a JSON schema (draft-07 compatible).
7
+ * Throws ConfigValidationError if validation errors are detected.
8
+ */
9
+ export declare function validateCompanionConfig(config: unknown, schema: any, path?: string): void;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigValidationError = void 0;
4
+ exports.validateCompanionConfig = validateCompanionConfig;
5
+ class ConfigValidationError extends Error {
6
+ errors;
7
+ constructor(errors) {
8
+ super(`Siduri configuration validation failed:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
9
+ this.name = 'ConfigValidationError';
10
+ this.errors = errors;
11
+ }
12
+ }
13
+ exports.ConfigValidationError = ConfigValidationError;
14
+ /**
15
+ * Validates a companion configuration object against a JSON schema (draft-07 compatible).
16
+ * Throws ConfigValidationError if validation errors are detected.
17
+ */
18
+ function validateCompanionConfig(config, schema, path = '$') {
19
+ const errors = [];
20
+ function validateNode(value, nodeSchema, curPath) {
21
+ if (!nodeSchema || typeof nodeSchema !== 'object')
22
+ return;
23
+ // Type validation
24
+ if (nodeSchema.type !== undefined) {
25
+ const type = nodeSchema.type;
26
+ if (type === 'object') {
27
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
28
+ errors.push(`${curPath}: expected object, received ${value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value}`);
29
+ return;
30
+ }
31
+ }
32
+ else if (type === 'array') {
33
+ if (!Array.isArray(value)) {
34
+ errors.push(`${curPath}: expected array, received ${typeof value}`);
35
+ return;
36
+ }
37
+ }
38
+ else if (type === 'string') {
39
+ if (typeof value !== 'string') {
40
+ errors.push(`${curPath}: expected string, received ${typeof value}`);
41
+ return;
42
+ }
43
+ }
44
+ else if (type === 'number') {
45
+ if (typeof value !== 'number' || Number.isNaN(value)) {
46
+ errors.push(`${curPath}: expected number, received ${typeof value}`);
47
+ return;
48
+ }
49
+ }
50
+ else if (type === 'integer') {
51
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
52
+ errors.push(`${curPath}: expected integer, received ${typeof value}`);
53
+ return;
54
+ }
55
+ }
56
+ else if (type === 'boolean') {
57
+ if (typeof value !== 'boolean') {
58
+ errors.push(`${curPath}: expected boolean, received ${typeof value}`);
59
+ return;
60
+ }
61
+ }
62
+ }
63
+ // Enum validation
64
+ if (Array.isArray(nodeSchema.enum)) {
65
+ if (!nodeSchema.enum.includes(value)) {
66
+ errors.push(`${curPath}: invalid value ${JSON.stringify(value)}, expected one of: ${nodeSchema.enum.map((v) => JSON.stringify(v)).join(', ')}`);
67
+ return;
68
+ }
69
+ }
70
+ // Object properties validation
71
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
72
+ if (Array.isArray(nodeSchema.required)) {
73
+ for (const reqKey of nodeSchema.required) {
74
+ if (value[reqKey] === undefined) {
75
+ errors.push(`${curPath}.${reqKey}: is required`);
76
+ }
77
+ }
78
+ }
79
+ const definedProps = nodeSchema.properties || {};
80
+ if (nodeSchema.additionalProperties === false) {
81
+ for (const key of Object.keys(value)) {
82
+ // Allow $schema property at root level
83
+ if (curPath === '$' && key === '$schema')
84
+ continue;
85
+ if (!(key in definedProps)) {
86
+ errors.push(`${curPath}.${key}: unexpected property is not allowed`);
87
+ }
88
+ }
89
+ }
90
+ for (const [propName, propSchema] of Object.entries(definedProps)) {
91
+ if (value[propName] !== undefined) {
92
+ validateNode(value[propName], propSchema, `${curPath}.${propName}`);
93
+ }
94
+ }
95
+ }
96
+ // Array items validation
97
+ if (Array.isArray(value) && nodeSchema.items) {
98
+ value.forEach((item, index) => {
99
+ validateNode(item, nodeSchema.items, `${curPath}[${index}]`);
100
+ });
101
+ }
102
+ }
103
+ validateNode(config, schema, path);
104
+ if (errors.length > 0) {
105
+ throw new ConfigValidationError(errors);
106
+ }
107
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const schema_validator_1 = require("./schema-validator");
4
+ describe('validateCompanionConfig', () => {
5
+ const sampleSchema = {
6
+ type: 'object',
7
+ required: ['id', 'name', 'organs'],
8
+ additionalProperties: false,
9
+ properties: {
10
+ $schema: { type: 'string' },
11
+ id: { type: 'string' },
12
+ name: { type: 'string' },
13
+ organs: {
14
+ type: 'object',
15
+ additionalProperties: false,
16
+ properties: {
17
+ brain: {
18
+ type: 'object',
19
+ required: ['provider', 'model'],
20
+ properties: {
21
+ provider: {
22
+ type: 'string',
23
+ enum: ['openrouter', 'openai-compatible'],
24
+ },
25
+ model: { type: 'string' },
26
+ },
27
+ },
28
+ memory: {
29
+ type: 'object',
30
+ required: ['provider'],
31
+ properties: {
32
+ provider: {
33
+ type: 'string',
34
+ enum: ['postgres', 'in-memory', 'none'],
35
+ },
36
+ maxConnections: { type: 'number' },
37
+ },
38
+ },
39
+ },
40
+ },
41
+ },
42
+ };
43
+ test('accepts valid configuration matching schema', () => {
44
+ const validConfig = {
45
+ $schema: './siduri.schema.json',
46
+ id: 'companion-1',
47
+ name: 'Test Companion',
48
+ organs: {
49
+ brain: {
50
+ provider: 'openrouter',
51
+ model: 'gpt-4o',
52
+ },
53
+ },
54
+ };
55
+ expect(() => (0, schema_validator_1.validateCompanionConfig)(validConfig, sampleSchema)).not.toThrow();
56
+ });
57
+ test('throws ConfigValidationError if missing required root field', () => {
58
+ const invalidConfig = {
59
+ name: 'Missing Id and Organs',
60
+ };
61
+ expect(() => (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema)).toThrow(schema_validator_1.ConfigValidationError);
62
+ try {
63
+ (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema);
64
+ }
65
+ catch (err) {
66
+ expect(err.errors).toContain('$.id: is required');
67
+ expect(err.errors).toContain('$.organs: is required');
68
+ }
69
+ });
70
+ test('throws ConfigValidationError on unexpected property when additionalProperties is false', () => {
71
+ const invalidConfig = {
72
+ id: 'c-1',
73
+ name: 'Test',
74
+ organs: {},
75
+ extraField: 'not allowed',
76
+ };
77
+ expect(() => (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema)).toThrow(schema_validator_1.ConfigValidationError);
78
+ try {
79
+ (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema);
80
+ }
81
+ catch (err) {
82
+ expect(err.errors).toContain('$.extraField: unexpected property is not allowed');
83
+ }
84
+ });
85
+ test('throws ConfigValidationError on invalid enum value', () => {
86
+ const invalidConfig = {
87
+ id: 'c-1',
88
+ name: 'Test',
89
+ organs: {
90
+ brain: {
91
+ provider: 'invalid-brain-provider',
92
+ model: 'gpt-4',
93
+ },
94
+ },
95
+ };
96
+ expect(() => (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema)).toThrow(schema_validator_1.ConfigValidationError);
97
+ try {
98
+ (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema);
99
+ }
100
+ catch (err) {
101
+ expect(err.errors.some((e) => e.includes('invalid value "invalid-brain-provider"'))).toBe(true);
102
+ }
103
+ });
104
+ test('throws ConfigValidationError on invalid type', () => {
105
+ const invalidConfig = {
106
+ id: 12345, // should be string
107
+ name: 'Test',
108
+ organs: {
109
+ memory: {
110
+ provider: 'postgres',
111
+ maxConnections: 'ten', // should be number
112
+ },
113
+ },
114
+ };
115
+ expect(() => (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema)).toThrow(schema_validator_1.ConfigValidationError);
116
+ try {
117
+ (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema);
118
+ }
119
+ catch (err) {
120
+ expect(err.errors).toContain('$.id: expected string, received number');
121
+ expect(err.errors).toContain('$.organs.memory.maxConnections: expected number, received string');
122
+ }
123
+ });
124
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siduri-x/core",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Core runtime types, evidence protocol, action dispatcher, capability validation, and SiduriRuntime protocol",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {