@unrdf/hooks 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.
Files changed (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/package.json +70 -0
  4. package/src/hooks/builtin-hooks.mjs +296 -0
  5. package/src/hooks/condition-cache.mjs +109 -0
  6. package/src/hooks/condition-evaluator.mjs +722 -0
  7. package/src/hooks/define-hook.mjs +211 -0
  8. package/src/hooks/effect-sandbox-worker.mjs +170 -0
  9. package/src/hooks/effect-sandbox.mjs +517 -0
  10. package/src/hooks/file-resolver.mjs +387 -0
  11. package/src/hooks/hook-chain-compiler.mjs +236 -0
  12. package/src/hooks/hook-executor-batching.mjs +277 -0
  13. package/src/hooks/hook-executor.mjs +465 -0
  14. package/src/hooks/hook-management.mjs +202 -0
  15. package/src/hooks/hook-scheduler.mjs +413 -0
  16. package/src/hooks/knowledge-hook-engine.mjs +358 -0
  17. package/src/hooks/knowledge-hook-manager.mjs +269 -0
  18. package/src/hooks/observability.mjs +531 -0
  19. package/src/hooks/policy-pack.mjs +572 -0
  20. package/src/hooks/quad-pool.mjs +249 -0
  21. package/src/hooks/quality-metrics.mjs +544 -0
  22. package/src/hooks/security/error-sanitizer.mjs +257 -0
  23. package/src/hooks/security/path-validator.mjs +194 -0
  24. package/src/hooks/security/sandbox-restrictions.mjs +331 -0
  25. package/src/hooks/telemetry.mjs +167 -0
  26. package/src/index.mjs +101 -0
  27. package/src/security/sandbox/browser-executor.mjs +220 -0
  28. package/src/security/sandbox/detector.mjs +342 -0
  29. package/src/security/sandbox/isolated-vm-executor.mjs +373 -0
  30. package/src/security/sandbox/vm2-executor.mjs +217 -0
  31. package/src/security/sandbox/worker-executor-runtime.mjs +74 -0
  32. package/src/security/sandbox/worker-executor.mjs +212 -0
  33. package/src/security/sandbox-adapter.mjs +141 -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
+ }