@unrdf/knowledge-engine 5.0.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.
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/package.json +64 -0
- package/src/browser-shims.mjs +343 -0
- package/src/browser.mjs +910 -0
- package/src/canonicalize.mjs +414 -0
- package/src/condition-cache.mjs +109 -0
- package/src/condition-evaluator.mjs +722 -0
- package/src/dark-matter-core.mjs +742 -0
- package/src/define-hook.mjs +213 -0
- package/src/effect-sandbox-browser.mjs +283 -0
- package/src/effect-sandbox-worker.mjs +170 -0
- package/src/effect-sandbox.mjs +517 -0
- package/src/engines/index.mjs +11 -0
- package/src/engines/rdf-engine.mjs +299 -0
- package/src/file-resolver.mjs +387 -0
- package/src/hook-executor-batching.mjs +277 -0
- package/src/hook-executor.mjs +870 -0
- package/src/hook-management.mjs +150 -0
- package/src/index.mjs +93 -0
- package/src/ken-parliment.mjs +119 -0
- package/src/ken.mjs +149 -0
- package/src/knowledge-engine/builtin-rules.mjs +190 -0
- package/src/knowledge-engine/inference-engine.mjs +418 -0
- package/src/knowledge-engine/knowledge-engine.mjs +317 -0
- package/src/knowledge-engine/pattern-dsl.mjs +142 -0
- package/src/knowledge-engine/pattern-matcher.mjs +215 -0
- package/src/knowledge-engine/rules.mjs +184 -0
- package/src/knowledge-engine.mjs +319 -0
- package/src/knowledge-hook-engine.mjs +360 -0
- package/src/knowledge-hook-manager.mjs +469 -0
- package/src/knowledge-substrate-core.mjs +927 -0
- package/src/lite.mjs +222 -0
- package/src/lockchain-writer-browser.mjs +414 -0
- package/src/lockchain-writer.mjs +602 -0
- package/src/monitoring/andon-signals.mjs +775 -0
- package/src/observability.mjs +531 -0
- package/src/parse.mjs +290 -0
- package/src/performance-optimizer.mjs +678 -0
- package/src/policy-pack.mjs +572 -0
- package/src/query-cache.mjs +116 -0
- package/src/query-optimizer.mjs +1051 -0
- package/src/query.mjs +306 -0
- package/src/reason.mjs +350 -0
- package/src/resolution-layer.mjs +506 -0
- package/src/schemas.mjs +1063 -0
- package/src/security/error-sanitizer.mjs +257 -0
- package/src/security/path-validator.mjs +194 -0
- package/src/security/sandbox-restrictions.mjs +331 -0
- package/src/security-validator.mjs +389 -0
- package/src/store-cache.mjs +137 -0
- package/src/telemetry.mjs +167 -0
- package/src/transaction.mjs +810 -0
- package/src/utils/adaptive-monitor.mjs +746 -0
- package/src/utils/circuit-breaker.mjs +513 -0
- package/src/utils/edge-case-handler.mjs +503 -0
- package/src/utils/memory-manager.mjs +498 -0
- package/src/utils/ring-buffer.mjs +282 -0
- package/src/validate.mjs +319 -0
- package/src/validators/index.mjs +338 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Policy Pack abstraction for versioned governance units
|
|
3
|
+
* @module policy-pack
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* Policy packs bundle related knowledge hooks into versioned, portable
|
|
7
|
+
* governance units that can be activated/deactivated as cohesive sets.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, _writeFileSync, existsSync, _mkdirSync, readdirSync } from 'fs';
|
|
11
|
+
import { join, dirname, _basename, _extname } from 'path';
|
|
12
|
+
import { _createKnowledgeHook, validateKnowledgeHook } from './schemas.mjs';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Schema for policy pack metadata
|
|
18
|
+
*/
|
|
19
|
+
const PolicyPackMetaSchema = z.object({
|
|
20
|
+
name: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.max(100)
|
|
24
|
+
.regex(
|
|
25
|
+
/^[a-zA-Z0-9:_-]+$/,
|
|
26
|
+
'Name must contain only alphanumeric characters, colons, hyphens, and underscores'
|
|
27
|
+
),
|
|
28
|
+
version: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semantic version format'),
|
|
29
|
+
description: z.string().min(1).max(1000).optional(),
|
|
30
|
+
author: z.string().min(1).max(100).optional(),
|
|
31
|
+
license: z.string().min(1).max(100).optional(),
|
|
32
|
+
tags: z.array(z.string().min(1).max(50)).max(20).optional(),
|
|
33
|
+
ontology: z.array(z.string().min(1).max(100)).max(10).optional(),
|
|
34
|
+
dependencies: z
|
|
35
|
+
.array(
|
|
36
|
+
z.object({
|
|
37
|
+
name: z.string().min(1),
|
|
38
|
+
version: z.string().min(1),
|
|
39
|
+
required: z.boolean().default(true),
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
.max(20)
|
|
43
|
+
.optional(),
|
|
44
|
+
createdAt: z.coerce.date().optional(),
|
|
45
|
+
updatedAt: z.coerce.date().optional(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Schema for policy pack configuration
|
|
50
|
+
*/
|
|
51
|
+
const PolicyPackConfigSchema = z.object({
|
|
52
|
+
enabled: z.boolean().default(true),
|
|
53
|
+
priority: z.number().int().min(0).max(100).default(50),
|
|
54
|
+
strictMode: z.boolean().default(false),
|
|
55
|
+
timeout: z.number().int().positive().max(300000).default(30000),
|
|
56
|
+
retries: z.number().int().nonnegative().max(5).default(1),
|
|
57
|
+
conditions: z
|
|
58
|
+
.object({
|
|
59
|
+
environment: z.array(z.string()).optional(),
|
|
60
|
+
version: z.string().optional(),
|
|
61
|
+
features: z.array(z.string()).optional(),
|
|
62
|
+
})
|
|
63
|
+
.optional(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Schema for policy pack manifest
|
|
68
|
+
*/
|
|
69
|
+
const PolicyPackManifestSchema = z.object({
|
|
70
|
+
id: z.string().uuid(),
|
|
71
|
+
meta: PolicyPackMetaSchema,
|
|
72
|
+
config: PolicyPackConfigSchema,
|
|
73
|
+
hooks: z.array(
|
|
74
|
+
z.object({
|
|
75
|
+
name: z.string().min(1),
|
|
76
|
+
file: z.string().min(1),
|
|
77
|
+
enabled: z.boolean().default(true),
|
|
78
|
+
priority: z.number().int().min(0).max(100).default(50),
|
|
79
|
+
})
|
|
80
|
+
),
|
|
81
|
+
conditions: z
|
|
82
|
+
.array(
|
|
83
|
+
z.object({
|
|
84
|
+
name: z.string().min(1),
|
|
85
|
+
file: z.string().min(1),
|
|
86
|
+
type: z.enum(['sparql-ask', 'sparql-select', 'shacl']),
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
.optional(),
|
|
90
|
+
resources: z
|
|
91
|
+
.array(
|
|
92
|
+
z.object({
|
|
93
|
+
name: z.string().min(1),
|
|
94
|
+
file: z.string().min(1),
|
|
95
|
+
type: z.enum(['ontology', 'vocabulary', 'data', 'other']),
|
|
96
|
+
})
|
|
97
|
+
)
|
|
98
|
+
.optional(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Policy Pack class for managing versioned governance units
|
|
103
|
+
*/
|
|
104
|
+
export class PolicyPack {
|
|
105
|
+
/**
|
|
106
|
+
* Create a new policy pack
|
|
107
|
+
* @param {Object} manifest - Policy pack manifest
|
|
108
|
+
* @param {string} [basePath] - Base path for file resolution
|
|
109
|
+
*/
|
|
110
|
+
constructor(manifest, basePath = process.cwd()) {
|
|
111
|
+
this.basePath = basePath;
|
|
112
|
+
this.manifest = PolicyPackManifestSchema.parse(manifest);
|
|
113
|
+
this.hooks = new Map();
|
|
114
|
+
this.conditions = new Map();
|
|
115
|
+
this.resources = new Map();
|
|
116
|
+
this.loaded = false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Load the policy pack from filesystem
|
|
121
|
+
* @returns {Promise<void>}
|
|
122
|
+
*/
|
|
123
|
+
async load() {
|
|
124
|
+
if (this.loaded) return;
|
|
125
|
+
|
|
126
|
+
const packPath = join(this.basePath, 'policy-packs', this.manifest.meta.name);
|
|
127
|
+
|
|
128
|
+
// Load hooks
|
|
129
|
+
for (const hookDef of this.manifest.hooks) {
|
|
130
|
+
if (!hookDef.enabled) continue;
|
|
131
|
+
|
|
132
|
+
const hookFile = join(packPath, hookDef.file);
|
|
133
|
+
if (!existsSync(hookFile)) {
|
|
134
|
+
throw new Error(`Hook file not found: ${hookFile}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const hookModule = await import(`file://${hookFile}`);
|
|
138
|
+
const hook = hookModule.default || hookModule;
|
|
139
|
+
|
|
140
|
+
// Validate hook
|
|
141
|
+
const validation = validateKnowledgeHook(hook);
|
|
142
|
+
if (!validation.success) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Invalid hook ${hookDef.name}: ${validation.errors.map(e => e.message).join(', ')}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Set priority from manifest
|
|
149
|
+
hook.priority = hookDef.priority;
|
|
150
|
+
|
|
151
|
+
this.hooks.set(hookDef.name, hook);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Load conditions
|
|
155
|
+
if (this.manifest.conditions) {
|
|
156
|
+
for (const conditionDef of this.manifest.conditions) {
|
|
157
|
+
const conditionFile = join(packPath, conditionDef.file);
|
|
158
|
+
if (!existsSync(conditionFile)) {
|
|
159
|
+
throw new Error(`Condition file not found: ${conditionFile}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const conditionContent = readFileSync(conditionFile, 'utf8');
|
|
163
|
+
this.conditions.set(conditionDef.name, {
|
|
164
|
+
content: conditionContent,
|
|
165
|
+
type: conditionDef.type,
|
|
166
|
+
file: conditionDef.file,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Load resources
|
|
172
|
+
if (this.manifest.resources) {
|
|
173
|
+
for (const resourceDef of this.manifest.resources) {
|
|
174
|
+
const resourceFile = join(packPath, resourceDef.file);
|
|
175
|
+
if (!existsSync(resourceFile)) {
|
|
176
|
+
throw new Error(`Resource file not found: ${resourceFile}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const resourceContent = readFileSync(resourceFile, 'utf8');
|
|
180
|
+
this.resources.set(resourceDef.name, {
|
|
181
|
+
content: resourceContent,
|
|
182
|
+
type: resourceDef.type,
|
|
183
|
+
file: resourceDef.file,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.loaded = true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get all hooks in this policy pack
|
|
193
|
+
* @returns {Array} Array of hook definitions
|
|
194
|
+
*/
|
|
195
|
+
getHooks() {
|
|
196
|
+
if (!this.loaded) {
|
|
197
|
+
throw new Error('Policy pack not loaded. Call load() first.');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return Array.from(this.hooks.values());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get a specific hook by name
|
|
205
|
+
* @param {string} name - Hook name
|
|
206
|
+
* @returns {Object} Hook definition or null
|
|
207
|
+
*/
|
|
208
|
+
getHook(name) {
|
|
209
|
+
if (!this.loaded) {
|
|
210
|
+
throw new Error('Policy pack not loaded. Call load() first.');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return this.hooks.get(name) || null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get all conditions in this policy pack
|
|
218
|
+
* @returns {Array} Array of condition definitions
|
|
219
|
+
*/
|
|
220
|
+
getConditions() {
|
|
221
|
+
if (!this.loaded) {
|
|
222
|
+
throw new Error('Policy pack not loaded. Call load() first.');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return Array.from(this.conditions.values());
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get a specific condition by name
|
|
230
|
+
* @param {string} name - Condition name
|
|
231
|
+
* @returns {Object} Condition definition or null
|
|
232
|
+
*/
|
|
233
|
+
getCondition(name) {
|
|
234
|
+
if (!this.loaded) {
|
|
235
|
+
throw new Error('Policy pack not loaded. Call load() first.');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return this.conditions.get(name) || null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get all resources in this policy pack
|
|
243
|
+
* @returns {Array} Array of resource definitions
|
|
244
|
+
*/
|
|
245
|
+
getResources() {
|
|
246
|
+
if (!this.loaded) {
|
|
247
|
+
throw new Error('Policy pack not loaded. Call load() first.');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return Array.from(this.resources.values());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get a specific resource by name
|
|
255
|
+
* @param {string} name - Resource name
|
|
256
|
+
* @returns {Object} Resource definition or null
|
|
257
|
+
*/
|
|
258
|
+
getResource(name) {
|
|
259
|
+
if (!this.loaded) {
|
|
260
|
+
throw new Error('Policy pack not loaded. Call load() first.');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return this.resources.get(name) || null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if this policy pack is compatible with the current environment
|
|
268
|
+
* @param {Object} [environment] - Environment information
|
|
269
|
+
* @returns {Object} Compatibility check result
|
|
270
|
+
*/
|
|
271
|
+
checkCompatibility(environment = {}) {
|
|
272
|
+
const result = {
|
|
273
|
+
compatible: true,
|
|
274
|
+
issues: [],
|
|
275
|
+
warnings: [],
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Check version compatibility
|
|
279
|
+
if (this.manifest.config.conditions?.version) {
|
|
280
|
+
const requiredVersion = this.manifest.config.conditions.version;
|
|
281
|
+
const currentVersion = environment.version || '1.0.0';
|
|
282
|
+
|
|
283
|
+
if (!this._isVersionCompatible(currentVersion, requiredVersion)) {
|
|
284
|
+
result.compatible = false;
|
|
285
|
+
result.issues.push(
|
|
286
|
+
`Version ${currentVersion} is not compatible with required ${requiredVersion}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Check environment compatibility
|
|
292
|
+
if (this.manifest.config.conditions?.environment) {
|
|
293
|
+
const requiredEnvs = this.manifest.config.conditions.environment;
|
|
294
|
+
const currentEnv = environment.environment || 'development';
|
|
295
|
+
|
|
296
|
+
if (!requiredEnvs.includes(currentEnv)) {
|
|
297
|
+
result.warnings.push(
|
|
298
|
+
`Environment ${currentEnv} not in required list: ${requiredEnvs.join(', ')}`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check feature compatibility
|
|
304
|
+
if (this.manifest.config.conditions?.features) {
|
|
305
|
+
const requiredFeatures = this.manifest.config.conditions.features;
|
|
306
|
+
const availableFeatures = environment.features || [];
|
|
307
|
+
|
|
308
|
+
for (const feature of requiredFeatures) {
|
|
309
|
+
if (!availableFeatures.includes(feature)) {
|
|
310
|
+
result.compatible = false;
|
|
311
|
+
result.issues.push(`Required feature ${feature} is not available`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get policy pack statistics
|
|
321
|
+
* @returns {Object} Statistics
|
|
322
|
+
*/
|
|
323
|
+
getStats() {
|
|
324
|
+
return {
|
|
325
|
+
id: this.manifest.id,
|
|
326
|
+
name: this.manifest.meta.name,
|
|
327
|
+
version: this.manifest.meta.version,
|
|
328
|
+
loaded: this.loaded,
|
|
329
|
+
hooks: {
|
|
330
|
+
total: this.manifest.hooks.length,
|
|
331
|
+
enabled: this.manifest.hooks.filter(h => h.enabled).length,
|
|
332
|
+
loaded: this.hooks.size,
|
|
333
|
+
},
|
|
334
|
+
conditions: {
|
|
335
|
+
total: this.manifest.conditions?.length || 0,
|
|
336
|
+
loaded: this.conditions.size,
|
|
337
|
+
},
|
|
338
|
+
resources: {
|
|
339
|
+
total: this.manifest.resources?.length || 0,
|
|
340
|
+
loaded: this.resources.size,
|
|
341
|
+
},
|
|
342
|
+
config: this.manifest.config,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Check if version is compatible
|
|
348
|
+
* @param {string} current - Current version
|
|
349
|
+
* @param {string} required - Required version
|
|
350
|
+
* @returns {boolean} Is compatible
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
_isVersionCompatible(current, required) {
|
|
354
|
+
// Simple version compatibility check
|
|
355
|
+
// In production, this would use proper semver parsing
|
|
356
|
+
const currentParts = current.split('.').map(Number);
|
|
357
|
+
const requiredParts = required.split('.').map(Number);
|
|
358
|
+
|
|
359
|
+
// Check major version compatibility
|
|
360
|
+
return currentParts[0] >= requiredParts[0];
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Policy Pack Manager for managing multiple policy packs
|
|
366
|
+
*/
|
|
367
|
+
export class PolicyPackManager {
|
|
368
|
+
/**
|
|
369
|
+
* Create a new policy pack manager
|
|
370
|
+
* @param {string} [basePath] - Base path for policy packs
|
|
371
|
+
*/
|
|
372
|
+
constructor(basePath = process.cwd()) {
|
|
373
|
+
this.basePath = basePath;
|
|
374
|
+
this.packs = new Map();
|
|
375
|
+
this.activePacks = new Set();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Load a policy pack from manifest file
|
|
380
|
+
* @param {string} manifestPath - Path to manifest file
|
|
381
|
+
* @returns {Promise<PolicyPack>} Loaded policy pack
|
|
382
|
+
*/
|
|
383
|
+
async loadPolicyPack(manifestPath) {
|
|
384
|
+
if (!existsSync(manifestPath)) {
|
|
385
|
+
throw new Error(`Manifest file not found: ${manifestPath}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const manifestContent = readFileSync(manifestPath, 'utf8');
|
|
389
|
+
const manifest = JSON.parse(manifestContent);
|
|
390
|
+
|
|
391
|
+
const pack = new PolicyPack(manifest, this.basePath);
|
|
392
|
+
await pack.load();
|
|
393
|
+
|
|
394
|
+
this.packs.set(pack.manifest.meta.name, pack);
|
|
395
|
+
return pack;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Load all policy packs from a directory
|
|
400
|
+
* @param {string} [packsDir] - Directory containing policy packs
|
|
401
|
+
* @returns {Promise<Array<PolicyPack>>} Array of loaded policy packs
|
|
402
|
+
*/
|
|
403
|
+
async loadAllPolicyPacks(packsDir = join(this.basePath, 'policy-packs')) {
|
|
404
|
+
if (!existsSync(packsDir)) {
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const packs = [];
|
|
409
|
+
const entries = readdirSync(packsDir, { withFileTypes: true });
|
|
410
|
+
|
|
411
|
+
for (const entry of entries) {
|
|
412
|
+
if (entry.isDirectory()) {
|
|
413
|
+
const manifestPath = join(packsDir, entry.name, 'manifest.json');
|
|
414
|
+
if (existsSync(manifestPath)) {
|
|
415
|
+
try {
|
|
416
|
+
const pack = await this.loadPolicyPack(manifestPath);
|
|
417
|
+
packs.push(pack);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.warn(`Failed to load policy pack ${entry.name}:`, error.message);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return packs;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Activate a policy pack
|
|
430
|
+
* @param {string} packName - Policy pack name
|
|
431
|
+
* @returns {boolean} Success
|
|
432
|
+
*/
|
|
433
|
+
activatePolicyPack(packName) {
|
|
434
|
+
const pack = this.packs.get(packName);
|
|
435
|
+
if (!pack) {
|
|
436
|
+
throw new Error(`Policy pack ${packName} not found`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!pack.manifest.config.enabled) {
|
|
440
|
+
throw new Error(`Policy pack ${packName} is disabled`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
this.activePacks.add(packName);
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Deactivate a policy pack
|
|
449
|
+
* @param {string} packName - Policy pack name
|
|
450
|
+
* @returns {boolean} Success
|
|
451
|
+
*/
|
|
452
|
+
deactivatePolicyPack(packName) {
|
|
453
|
+
return this.activePacks.delete(packName);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get all active policy packs
|
|
458
|
+
* @returns {Array<PolicyPack>} Array of active policy packs
|
|
459
|
+
*/
|
|
460
|
+
getActivePolicyPacks() {
|
|
461
|
+
return Array.from(this.activePacks)
|
|
462
|
+
.map(name => this.packs.get(name))
|
|
463
|
+
.filter(pack => pack !== undefined);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get all hooks from active policy packs
|
|
468
|
+
* @returns {Array} Array of hook definitions
|
|
469
|
+
*/
|
|
470
|
+
getActiveHooks() {
|
|
471
|
+
const hooks = [];
|
|
472
|
+
|
|
473
|
+
for (const packName of this.activePacks) {
|
|
474
|
+
const pack = this.packs.get(packName);
|
|
475
|
+
if (pack) {
|
|
476
|
+
hooks.push(...pack.getHooks());
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Sort by priority
|
|
481
|
+
return hooks.sort((a, b) => (b.priority || 50) - (a.priority || 50));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Get policy pack by name
|
|
486
|
+
* @param {string} name - Policy pack name
|
|
487
|
+
* @returns {PolicyPack} Policy pack or null
|
|
488
|
+
*/
|
|
489
|
+
getPolicyPack(name) {
|
|
490
|
+
return this.packs.get(name) || null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Get all policy packs
|
|
495
|
+
* @returns {Array<PolicyPack>} Array of all policy packs
|
|
496
|
+
*/
|
|
497
|
+
getAllPolicyPacks() {
|
|
498
|
+
return Array.from(this.packs.values());
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Get manager statistics
|
|
503
|
+
* @returns {Object} Statistics
|
|
504
|
+
*/
|
|
505
|
+
getStats() {
|
|
506
|
+
const _activePacks = this.getActivePolicyPacks();
|
|
507
|
+
const allHooks = this.getActiveHooks();
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
totalPacks: this.packs.size,
|
|
511
|
+
activePacks: this.activePacks.size,
|
|
512
|
+
totalHooks: allHooks.length,
|
|
513
|
+
packs: Array.from(this.packs.values()).map(pack => pack.getStats()),
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Create a policy pack from a directory structure
|
|
520
|
+
* @param {string} packDir - Directory containing policy pack files
|
|
521
|
+
* @returns {Promise<PolicyPack>} Created policy pack
|
|
522
|
+
*/
|
|
523
|
+
export async function createPolicyPackFromDirectory(packDir) {
|
|
524
|
+
const manifestPath = join(packDir, 'manifest.json');
|
|
525
|
+
|
|
526
|
+
if (!existsSync(manifestPath)) {
|
|
527
|
+
throw new Error(`Manifest file not found: ${manifestPath}`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const manager = new PolicyPackManager(dirname(packDir));
|
|
531
|
+
return manager.loadPolicyPack(manifestPath);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Create a new policy pack manifest
|
|
536
|
+
* @param {Object} options - Manifest options
|
|
537
|
+
* @returns {Object} Policy pack manifest
|
|
538
|
+
*/
|
|
539
|
+
export function createPolicyPackManifest(name, hooks, options = {}) {
|
|
540
|
+
const manifest = {
|
|
541
|
+
id: randomUUID(),
|
|
542
|
+
meta: {
|
|
543
|
+
name: name,
|
|
544
|
+
version: options.version || '1.0.0',
|
|
545
|
+
description: options.description,
|
|
546
|
+
author: options.author,
|
|
547
|
+
license: options.license || 'MIT',
|
|
548
|
+
tags: options.tags || [],
|
|
549
|
+
ontology: options.ontology || [],
|
|
550
|
+
dependencies: options.dependencies || [],
|
|
551
|
+
createdAt: new Date().toISOString(),
|
|
552
|
+
},
|
|
553
|
+
config: {
|
|
554
|
+
enabled: options.enabled !== false,
|
|
555
|
+
priority: options.priority || 50,
|
|
556
|
+
strictMode: options.strictMode || false,
|
|
557
|
+
timeout: options.timeout || 30000,
|
|
558
|
+
retries: options.retries || 1,
|
|
559
|
+
conditions: options.conditions || {},
|
|
560
|
+
},
|
|
561
|
+
hooks: hooks.map(hook => ({
|
|
562
|
+
name: hook.meta.name,
|
|
563
|
+
file: `${hook.meta.name}.mjs`,
|
|
564
|
+
enabled: true,
|
|
565
|
+
priority: hook.priority || 50,
|
|
566
|
+
})),
|
|
567
|
+
conditions: options.conditions || [],
|
|
568
|
+
resources: options.resources || [],
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
return PolicyPackManifestSchema.parse(manifest);
|
|
572
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Query Cache (Legacy - Deprecated)
|
|
3
|
+
* @module query-cache
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* This module has been deprecated with the migration from Comunica to Oxigraph.
|
|
7
|
+
* Oxigraph uses synchronous execution with <1ms cold start, eliminating the need
|
|
8
|
+
* for QueryEngine initialization caching.
|
|
9
|
+
*
|
|
10
|
+
* Maintained for backward compatibility only.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* LRU cache for file content (keyed by SHA-256)
|
|
17
|
+
* @type {Map<string, string>}
|
|
18
|
+
*/
|
|
19
|
+
const fileContentCache = new Map();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maximum cache entries
|
|
23
|
+
*/
|
|
24
|
+
const MAX_FILE_CACHE = 50;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Cache statistics
|
|
28
|
+
*/
|
|
29
|
+
const stats = {
|
|
30
|
+
fileCacheHits: 0,
|
|
31
|
+
fileCacheMisses: 0,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get query hash
|
|
36
|
+
*
|
|
37
|
+
* @param {string} sparql - SPARQL query string
|
|
38
|
+
* @returns {string} Query hash for caching
|
|
39
|
+
*/
|
|
40
|
+
export function getQueryHash(sparql) {
|
|
41
|
+
return createHash('sha256').update(sparql.trim()).digest('hex');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Cache file content by SHA-256 hash.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} sha256 - Content hash
|
|
48
|
+
* @param {string} content - File content
|
|
49
|
+
*/
|
|
50
|
+
export function cacheFileContent(sha256, content) {
|
|
51
|
+
// LRU eviction: Remove oldest entry if cache is full
|
|
52
|
+
if (fileContentCache.size >= MAX_FILE_CACHE && !fileContentCache.has(sha256)) {
|
|
53
|
+
const firstKey = fileContentCache.keys().next().value;
|
|
54
|
+
fileContentCache.delete(firstKey);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fileContentCache.set(sha256, content);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get cached file content by SHA-256 hash.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} sha256 - Content hash
|
|
64
|
+
* @returns {string|null} Cached content or null
|
|
65
|
+
*/
|
|
66
|
+
export function getCachedFileContent(sha256) {
|
|
67
|
+
if (fileContentCache.has(sha256)) {
|
|
68
|
+
stats.fileCacheHits++;
|
|
69
|
+
return fileContentCache.get(sha256);
|
|
70
|
+
}
|
|
71
|
+
stats.fileCacheMisses++;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get cache statistics.
|
|
77
|
+
*
|
|
78
|
+
* @returns {Object} Cache statistics
|
|
79
|
+
*/
|
|
80
|
+
export function getCacheStats() {
|
|
81
|
+
return {
|
|
82
|
+
...stats,
|
|
83
|
+
fileCacheSize: fileContentCache.size,
|
|
84
|
+
fileCacheHitRate: stats.fileCacheHits / (stats.fileCacheHits + stats.fileCacheMisses) || 0,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Clear all caches (for testing).
|
|
90
|
+
*/
|
|
91
|
+
export function clearCaches() {
|
|
92
|
+
fileContentCache.clear();
|
|
93
|
+
stats.fileCacheHits = 0;
|
|
94
|
+
stats.fileCacheMisses = 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Shutdown hook for cleanup.
|
|
99
|
+
* Should be called when process is exiting.
|
|
100
|
+
*/
|
|
101
|
+
export function shutdown() {
|
|
102
|
+
clearCaches();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Register shutdown hook
|
|
106
|
+
if (typeof process !== 'undefined') {
|
|
107
|
+
process.on('exit', shutdown);
|
|
108
|
+
process.on('SIGINT', () => {
|
|
109
|
+
shutdown();
|
|
110
|
+
process.exit(0);
|
|
111
|
+
});
|
|
112
|
+
process.on('SIGTERM', () => {
|
|
113
|
+
shutdown();
|
|
114
|
+
process.exit(0);
|
|
115
|
+
});
|
|
116
|
+
}
|