@unrdf/hooks 5.0.1 → 26.4.2

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 (40) hide show
  1. package/dist/index.d.mts +1738 -0
  2. package/dist/index.d.ts +1738 -0
  3. package/dist/index.mjs +1738 -0
  4. package/examples/basic.mjs +113 -0
  5. package/examples/hook-chains/README.md +263 -0
  6. package/examples/hook-chains/node_modules/.bin/validate-hooks +21 -0
  7. package/examples/hook-chains/node_modules/.bin/vitest +21 -0
  8. package/examples/hook-chains/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  9. package/examples/hook-chains/package.json +25 -0
  10. package/examples/hook-chains/src/index.mjs +348 -0
  11. package/examples/hook-chains/test/example.test.mjs +252 -0
  12. package/examples/hook-chains/vitest.config.mjs +14 -0
  13. package/examples/knowledge-hook-manager-usage.mjs +65 -0
  14. package/examples/policy-hooks/README.md +193 -0
  15. package/examples/policy-hooks/node_modules/.bin/validate-hooks +21 -0
  16. package/examples/policy-hooks/node_modules/.bin/vitest +21 -0
  17. package/examples/policy-hooks/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  18. package/examples/policy-hooks/package.json +25 -0
  19. package/examples/policy-hooks/src/index.mjs +275 -0
  20. package/examples/policy-hooks/test/example.test.mjs +204 -0
  21. package/examples/policy-hooks/vitest.config.mjs +14 -0
  22. package/examples/validate-hooks.mjs +154 -0
  23. package/package.json +29 -24
  24. package/src/hooks/builtin-hooks.mjs +72 -48
  25. package/src/hooks/condition-evaluator.mjs +1 -1
  26. package/src/hooks/define-hook.mjs +25 -9
  27. package/src/hooks/effect-sandbox-worker.mjs +1 -1
  28. package/src/hooks/effect-sandbox.mjs +5 -2
  29. package/src/hooks/file-resolver.mjs +2 -2
  30. package/src/hooks/hook-executor.mjs +12 -19
  31. package/src/hooks/policy-pack.mjs +3 -3
  32. package/src/hooks/query-optimizer.mjs +196 -0
  33. package/src/hooks/query.mjs +150 -0
  34. package/src/hooks/schemas.mjs +158 -0
  35. package/src/hooks/security/path-validator.mjs +1 -1
  36. package/src/hooks/security/sandbox-restrictions.mjs +2 -2
  37. package/src/hooks/store-cache.mjs +189 -0
  38. package/src/hooks/validate.mjs +133 -0
  39. package/src/index.mjs +62 -0
  40. package/src/policy-compiler.mjs +503 -0
@@ -0,0 +1,1738 @@
1
+ import { z } from 'zod';
2
+ import { dataFactory } from '@unrdf/oxigraph';
3
+
4
+ const HookTriggerSchema = z.enum([
5
+ // Core CRUD (6)
6
+ "before-add",
7
+ "after-add",
8
+ "before-query",
9
+ "after-query",
10
+ "before-remove",
11
+ "after-remove",
12
+ // Transaction Hooks (4)
13
+ "before-commit",
14
+ "after-commit",
15
+ "before-rollback",
16
+ "after-rollback",
17
+ // Error/Event Hooks (5)
18
+ "on-error",
19
+ "on-validation-fail",
20
+ "on-transform",
21
+ "on-timeout",
22
+ "on-circuit-open",
23
+ // Async/IO Hooks (6)
24
+ "before-fetch",
25
+ "after-fetch",
26
+ "before-sync",
27
+ "after-sync",
28
+ "before-import",
29
+ "after-import",
30
+ // Cron/Time Hooks (4)
31
+ "on-schedule",
32
+ "on-interval",
33
+ "on-idle",
34
+ "on-startup",
35
+ // Lean Six Sigma Quality Hooks (8)
36
+ "quality-gate",
37
+ "defect-detection",
38
+ "continuous-improvement",
39
+ "spc-control",
40
+ "capability-analysis",
41
+ "root-cause",
42
+ "kaizen-event",
43
+ "audit-trail"
44
+ ]);
45
+ const HookConfigSchema = z.object({
46
+ name: z.string().min(1, "Hook name is required"),
47
+ trigger: HookTriggerSchema,
48
+ // Note: No return type enforcement - runtime POKA-YOKE guard handles non-boolean returns
49
+ validate: z.function().optional(),
50
+ transform: z.function().optional(),
51
+ metadata: z.record(z.string(), z.any()).optional()
52
+ });
53
+ const HookSchema = z.object({
54
+ name: z.string(),
55
+ trigger: HookTriggerSchema,
56
+ validate: z.function().optional(),
57
+ transform: z.function().optional(),
58
+ metadata: z.record(z.string(), z.any()).optional()
59
+ });
60
+ function defineHook(config) {
61
+ const validated = HookConfigSchema.parse(config);
62
+ if (!validated.validate && !validated.transform) {
63
+ throw new Error("Hook must define either validate or transform function");
64
+ }
65
+ return {
66
+ name: validated.name,
67
+ trigger: validated.trigger,
68
+ validate: validated.validate,
69
+ transform: validated.transform,
70
+ metadata: validated.metadata || {},
71
+ // Pre-computed flags for sub-1μs execution (skip Zod in hot path)
72
+ _hasValidation: typeof validated.validate === "function",
73
+ _hasTransformation: typeof validated.transform === "function",
74
+ _validated: true
75
+ };
76
+ }
77
+ function isValidHook(hook) {
78
+ try {
79
+ HookSchema.parse(hook);
80
+ return hook.validate !== void 0 || hook.transform !== void 0;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ function getHookMetadata(hook, key) {
86
+ const validated = HookSchema.parse(hook);
87
+ return validated.metadata?.[key];
88
+ }
89
+ function hasValidation$1(hook) {
90
+ if (hook._validated) {
91
+ return hook._hasValidation;
92
+ }
93
+ return typeof hook.validate === "function";
94
+ }
95
+ function hasTransformation$1(hook) {
96
+ if (hook._validated) {
97
+ return hook._hasTransformation;
98
+ }
99
+ return typeof hook.transform === "function";
100
+ }
101
+
102
+ const HookResultSchema = z.object({
103
+ valid: z.boolean(),
104
+ quad: z.any().optional(),
105
+ error: z.string().optional(),
106
+ hookName: z.string()
107
+ });
108
+ const ChainResultSchema = z.object({
109
+ valid: z.boolean(),
110
+ quad: z.any(),
111
+ results: z.array(HookResultSchema),
112
+ error: z.string().optional()
113
+ });
114
+ function executeHook(hook, quad, options = {}) {
115
+ const validatedHook = hook._validated ? hook : HookSchema.parse(hook);
116
+ const result = {
117
+ valid: true,
118
+ quad,
119
+ hookName: validatedHook.name
120
+ };
121
+ try {
122
+ if (hasValidation$1(validatedHook)) {
123
+ const validationResult = validatedHook.validate(quad);
124
+ if (typeof validationResult !== "boolean") {
125
+ console.warn(
126
+ `[POKA-YOKE] Hook "${validatedHook.name}": validate() returned ${typeof validationResult}, expected boolean. Coercing to boolean.`
127
+ );
128
+ result.warning = `Non-boolean validation return (${typeof validationResult}) coerced to boolean`;
129
+ }
130
+ if (!validationResult) {
131
+ result.valid = false;
132
+ result.error = `Validation failed for hook: ${validatedHook.name}`;
133
+ return result;
134
+ }
135
+ }
136
+ if (hasTransformation$1(validatedHook)) {
137
+ const transformed = validatedHook.transform(quad);
138
+ if (!transformed || typeof transformed !== "object") {
139
+ throw new TypeError(
140
+ `Hook "${validatedHook.name}": transform() must return a Quad object, got ${typeof transformed}`
141
+ );
142
+ }
143
+ if (!transformed.subject || !transformed.predicate || !transformed.object) {
144
+ throw new TypeError(
145
+ `Hook "${validatedHook.name}": transform() returned object missing subject/predicate/object`
146
+ );
147
+ }
148
+ if (transformed._pooled && options.warnPooledQuads !== false) {
149
+ console.warn(
150
+ `[POKA-YOKE] Hook "${validatedHook.name}": returned pooled quad. Clone before storing to prevent memory issues.`
151
+ );
152
+ result.warning = "Pooled quad returned - consider cloning";
153
+ }
154
+ result.quad = transformed;
155
+ }
156
+ return result;
157
+ } catch (error) {
158
+ result.valid = false;
159
+ result.error = error instanceof Error ? error.message : String(error);
160
+ result.errorDetails = {
161
+ hookName: validatedHook.name,
162
+ hookTrigger: validatedHook.trigger,
163
+ stack: error instanceof Error ? error.stack : void 0,
164
+ originalError: error instanceof Error ? error : void 0,
165
+ rawError: !(error instanceof Error) ? error : void 0
166
+ };
167
+ return result;
168
+ }
169
+ }
170
+ function executeHookChain(hooks, quad) {
171
+ const results = [];
172
+ let currentQuad = quad;
173
+ let chainValid = true;
174
+ let chainError = void 0;
175
+ for (const hook of hooks) {
176
+ const result = executeHook(hook, currentQuad);
177
+ results.push(result);
178
+ if (!result.valid) {
179
+ chainValid = false;
180
+ chainError = result.error;
181
+ break;
182
+ }
183
+ if (result.quad) {
184
+ currentQuad = result.quad;
185
+ }
186
+ }
187
+ return {
188
+ valid: chainValid,
189
+ quad: currentQuad,
190
+ results,
191
+ error: chainError
192
+ };
193
+ }
194
+ function executeHooksByTrigger(hooks, trigger, quad) {
195
+ const matchingHooks = hooks.filter((h) => h.trigger === trigger);
196
+ return executeHookChain(matchingHooks, quad);
197
+ }
198
+ function wouldPassHooks(hooks, quad) {
199
+ const result = executeHookChain(hooks, quad);
200
+ return result.valid;
201
+ }
202
+ function validateOnly(hooks, quad) {
203
+ for (const hook of hooks) {
204
+ if (hasValidation$1(hook)) {
205
+ try {
206
+ if (!hook.validate(quad)) {
207
+ return {
208
+ valid: false,
209
+ quad,
210
+ error: `Validation failed for hook: ${hook.name}`,
211
+ hookName: hook.name
212
+ };
213
+ }
214
+ } catch (error) {
215
+ return {
216
+ valid: false,
217
+ quad,
218
+ error: error instanceof Error ? error.message : String(error),
219
+ hookName: hook.name
220
+ };
221
+ }
222
+ }
223
+ }
224
+ return { valid: true, quad, hookName: "validateOnly" };
225
+ }
226
+ function executeBatch(hooks, quads, options = {}) {
227
+ const { stopOnError = false } = options;
228
+ const results = [];
229
+ let validCount = 0;
230
+ let invalidCount = 0;
231
+ for (let i = 0; i < quads.length; i++) {
232
+ const quad = quads[i];
233
+ let currentQuad = quad;
234
+ let isValid = true;
235
+ let error;
236
+ for (const hook of hooks) {
237
+ if (hasValidation$1(hook)) {
238
+ try {
239
+ if (!hook.validate(currentQuad)) {
240
+ isValid = false;
241
+ error = `Validation failed: ${hook.name}`;
242
+ break;
243
+ }
244
+ } catch (e) {
245
+ isValid = false;
246
+ error = e instanceof Error ? e.message : String(e);
247
+ break;
248
+ }
249
+ }
250
+ if (isValid && hasTransformation$1(hook)) {
251
+ try {
252
+ currentQuad = hook.transform(currentQuad);
253
+ } catch (e) {
254
+ isValid = false;
255
+ error = e instanceof Error ? e.message : String(e);
256
+ break;
257
+ }
258
+ }
259
+ }
260
+ results.push({ valid: isValid, quad: currentQuad, error, results: [] });
261
+ if (isValid) {
262
+ validCount++;
263
+ } else {
264
+ invalidCount++;
265
+ if (stopOnError) break;
266
+ }
267
+ }
268
+ return { results, validCount, invalidCount };
269
+ }
270
+ function validateBatch(hooks, quads) {
271
+ const validationHooks = hooks.filter(hasValidation$1);
272
+ const bitmap = new Uint8Array(quads.length);
273
+ for (let i = 0; i < quads.length; i++) {
274
+ const quad = quads[i];
275
+ let isValid = true;
276
+ for (const hook of validationHooks) {
277
+ try {
278
+ if (!hook.validate(quad)) {
279
+ isValid = false;
280
+ break;
281
+ }
282
+ } catch {
283
+ isValid = false;
284
+ break;
285
+ }
286
+ }
287
+ bitmap[i] = isValid ? 1 : 0;
288
+ }
289
+ return bitmap;
290
+ }
291
+ function transformBatch(hooks, quads, options = {}) {
292
+ const { validateFirst = true } = options;
293
+ const transformed = [];
294
+ const errors = [];
295
+ for (let i = 0; i < quads.length; i++) {
296
+ let currentQuad = quads[i];
297
+ let hasError = false;
298
+ for (const hook of hooks) {
299
+ try {
300
+ if (validateFirst && hasValidation$1(hook)) {
301
+ if (!hook.validate(currentQuad)) {
302
+ errors.push({ index: i, error: `Validation failed: ${hook.name}` });
303
+ hasError = true;
304
+ break;
305
+ }
306
+ }
307
+ if (hasTransformation$1(hook)) {
308
+ currentQuad = hook.transform(currentQuad);
309
+ }
310
+ } catch (error) {
311
+ errors.push({
312
+ index: i,
313
+ error: error instanceof Error ? error.message : String(error)
314
+ });
315
+ hasError = true;
316
+ break;
317
+ }
318
+ }
319
+ if (!hasError) {
320
+ transformed.push(currentQuad);
321
+ }
322
+ }
323
+ return { transformed, errors };
324
+ }
325
+ const hookValidationCache = /* @__PURE__ */ new WeakMap();
326
+ function clearHookCaches() {
327
+ }
328
+ function prewarmHookCache(hooks) {
329
+ const errors = [];
330
+ let prewarmed = 0;
331
+ for (const hook of hooks) {
332
+ try {
333
+ HookSchema.parse(hook);
334
+ hookValidationCache.set(hook, true);
335
+ prewarmed++;
336
+ } catch (error) {
337
+ errors.push(
338
+ `Hook "${hook?.name || "unknown"}": ${error instanceof Error ? error.message : String(error)}`
339
+ );
340
+ }
341
+ }
342
+ return { prewarmed, errors };
343
+ }
344
+
345
+ const compiledChains = /* @__PURE__ */ new Map();
346
+ let jitAvailable = true;
347
+ try {
348
+ new Function("return true")();
349
+ } catch {
350
+ jitAvailable = false;
351
+ }
352
+ function hasValidation(hook) {
353
+ return typeof hook.validate === "function";
354
+ }
355
+ function hasTransformation(hook) {
356
+ return typeof hook.transform === "function";
357
+ }
358
+ function getChainKey(hooks) {
359
+ return hooks.map((h) => h.name).join("|");
360
+ }
361
+ function compileHookChain(hooks) {
362
+ const chainKey = getChainKey(hooks);
363
+ if (compiledChains.has(chainKey)) {
364
+ return compiledChains.get(chainKey);
365
+ }
366
+ if (!jitAvailable) {
367
+ const interpretedFn = createInterpretedChain(hooks);
368
+ compiledChains.set(chainKey, interpretedFn);
369
+ return interpretedFn;
370
+ }
371
+ const validationSteps = hooks.map(
372
+ (h, i) => hasValidation(h) ? `if (!hooks[${i}].validate(quad)) return { valid: false, quad, failedHook: hooks[${i}].name };` : ""
373
+ ).filter(Boolean).join("\n ");
374
+ const transformSteps = hooks.map((h, i) => hasTransformation(h) ? `quad = hooks[${i}].transform(quad);` : "").filter(Boolean).join("\n ");
375
+ const fnBody = `
376
+ ${validationSteps}
377
+ ${transformSteps}
378
+ return { valid: true, quad };
379
+ `;
380
+ try {
381
+ const compiledFn = new Function("hooks", "quad", fnBody);
382
+ compiledChains.set(chainKey, compiledFn);
383
+ return compiledFn;
384
+ } catch {
385
+ jitAvailable = false;
386
+ const interpretedFn = createInterpretedChain(hooks);
387
+ compiledChains.set(chainKey, interpretedFn);
388
+ return interpretedFn;
389
+ }
390
+ }
391
+ function createInterpretedChain(hooks) {
392
+ const capturedHooks = hooks;
393
+ return function interpretedChain(_hooks, quad) {
394
+ let currentQuad = quad;
395
+ for (const hook of capturedHooks) {
396
+ if (hasValidation(hook)) {
397
+ if (!hook.validate(currentQuad)) {
398
+ return { valid: false, quad: currentQuad, failedHook: hook.name };
399
+ }
400
+ }
401
+ if (hasTransformation(hook)) {
402
+ currentQuad = hook.transform(currentQuad);
403
+ }
404
+ }
405
+ return { valid: true, quad: currentQuad };
406
+ };
407
+ }
408
+ function compileValidationOnlyChain(hooks) {
409
+ const chainKey = `validate:${getChainKey(hooks)}`;
410
+ if (compiledChains.has(chainKey)) {
411
+ return compiledChains.get(chainKey);
412
+ }
413
+ const validationHooks = hooks.filter(hasValidation);
414
+ if (!jitAvailable || validationHooks.length === 0) {
415
+ const fn = validationHooks.length === 0 ? () => true : (_hooks, quad) => validationHooks.every((h) => h.validate(quad));
416
+ compiledChains.set(chainKey, fn);
417
+ return fn;
418
+ }
419
+ const checks = validationHooks.map((_, i) => `hooks[${i}].validate(quad)`).join(" && ");
420
+ const fnBody = `return ${checks || "true"};`;
421
+ try {
422
+ const compiledFn = new Function("hooks", "quad", fnBody);
423
+ const wrapper = (_, quad) => compiledFn(validationHooks, quad);
424
+ compiledChains.set(chainKey, wrapper);
425
+ return wrapper;
426
+ } catch {
427
+ const fn = (_hooks, quad) => validationHooks.every((h) => h.validate(quad));
428
+ compiledChains.set(chainKey, fn);
429
+ return fn;
430
+ }
431
+ }
432
+ function clearCompiledChainCache() {
433
+ compiledChains.clear();
434
+ }
435
+ function getCompilerStats() {
436
+ return {
437
+ size: compiledChains.size,
438
+ jitAvailable
439
+ };
440
+ }
441
+ function isJitAvailable() {
442
+ return jitAvailable;
443
+ }
444
+
445
+ class QuadPool {
446
+ /**
447
+ * Create a new quad pool.
448
+ *
449
+ * @param {object} options - Pool options
450
+ * @param {number} options.size - Initial pool size (default: 1000)
451
+ * @param {boolean} options.autoGrow - Auto-grow pool when exhausted (default: true)
452
+ */
453
+ constructor(options = {}) {
454
+ this.size = options.size || 1e3;
455
+ this.autoGrow = options.autoGrow !== false;
456
+ this.pool = new Array(this.size);
457
+ this.index = 0;
458
+ this.acquired = 0;
459
+ this.highWaterMark = 0;
460
+ for (let i = 0; i < this.size; i++) {
461
+ this.pool[i] = this._createEmptyQuad();
462
+ }
463
+ }
464
+ /**
465
+ * Create an empty quad object for the pool.
466
+ *
467
+ * @private
468
+ * @returns {PooledQuad} - Empty quad object
469
+ */
470
+ _createEmptyQuad() {
471
+ return {
472
+ subject: null,
473
+ predicate: null,
474
+ object: null,
475
+ graph: null,
476
+ _pooled: true
477
+ };
478
+ }
479
+ /**
480
+ * Acquire a quad from the pool.
481
+ *
482
+ * @param {import('rdf-js').Term} subject - Subject term
483
+ * @param {import('rdf-js').Term} predicate - Predicate term
484
+ * @param {import('rdf-js').Term} object - Object term
485
+ * @param {import('rdf-js').Term} [graph] - Graph term (optional)
486
+ * @returns {PooledQuad} - Pooled quad with assigned values
487
+ *
488
+ * @example
489
+ * const quad = quadPool.acquire(subject, predicate, object, graph);
490
+ * // Use quad...
491
+ * quadPool.release(quad);
492
+ */
493
+ acquire(subject, predicate, object, graph = null) {
494
+ const quad = this.pool[this.index];
495
+ quad.subject = subject;
496
+ quad.predicate = predicate;
497
+ quad.object = object;
498
+ quad.graph = graph;
499
+ this.index = (this.index + 1) % this.size;
500
+ this.acquired++;
501
+ if (this.acquired > this.highWaterMark) {
502
+ this.highWaterMark = this.acquired;
503
+ }
504
+ if (this.autoGrow && this.acquired >= this.size) {
505
+ this._grow();
506
+ }
507
+ return quad;
508
+ }
509
+ /**
510
+ * Release a quad back to the pool.
511
+ *
512
+ * @param {PooledQuad} quad - Quad to release
513
+ */
514
+ release(quad) {
515
+ if (quad && quad._pooled) {
516
+ quad.subject = null;
517
+ quad.predicate = null;
518
+ quad.object = null;
519
+ quad.graph = null;
520
+ this.acquired = Math.max(0, this.acquired - 1);
521
+ }
522
+ }
523
+ /**
524
+ * Clone a pooled quad to a non-pooled object.
525
+ * Use this when you need to persist a quad beyond the current operation.
526
+ *
527
+ * @param {PooledQuad} quad - Quad to clone
528
+ * @param {Function} dataFactory - Data factory with quad() method
529
+ * @returns {import('rdf-js').Quad} - Non-pooled quad
530
+ */
531
+ clone(quad, dataFactory) {
532
+ return dataFactory.quad(quad.subject, quad.predicate, quad.object, quad.graph);
533
+ }
534
+ /**
535
+ * Grow the pool by doubling its size.
536
+ *
537
+ * @private
538
+ */
539
+ _grow() {
540
+ const newSize = this.size * 2;
541
+ const newPool = new Array(newSize);
542
+ for (let i = 0; i < this.size; i++) {
543
+ newPool[i] = this.pool[i];
544
+ }
545
+ for (let i = this.size; i < newSize; i++) {
546
+ newPool[i] = this._createEmptyQuad();
547
+ }
548
+ this.pool = newPool;
549
+ this.size = newSize;
550
+ }
551
+ /**
552
+ * Reset the pool (clear all references, reset index).
553
+ */
554
+ reset() {
555
+ for (let i = 0; i < this.size; i++) {
556
+ const quad = this.pool[i];
557
+ quad.subject = null;
558
+ quad.predicate = null;
559
+ quad.object = null;
560
+ quad.graph = null;
561
+ }
562
+ this.index = 0;
563
+ this.acquired = 0;
564
+ }
565
+ /**
566
+ * Get pool statistics.
567
+ *
568
+ * @returns {{size: number, acquired: number, available: number, highWaterMark: number}} - Pool stats
569
+ */
570
+ stats() {
571
+ return {
572
+ size: this.size,
573
+ acquired: this.acquired,
574
+ available: this.size - this.acquired,
575
+ highWaterMark: this.highWaterMark,
576
+ utilizationPercent: (this.acquired / this.size * 100).toFixed(1)
577
+ };
578
+ }
579
+ }
580
+ const quadPool = new QuadPool({ size: 1e3 });
581
+ function createPooledTransform(transformFn, pool = quadPool) {
582
+ return function pooledTransform(quad) {
583
+ const result = transformFn(quad);
584
+ if (result !== quad) {
585
+ return pool.acquire(result.subject, result.predicate, result.object, result.graph);
586
+ }
587
+ return quad;
588
+ };
589
+ }
590
+ function isPooledQuad(quad) {
591
+ return quad && quad._pooled === true;
592
+ }
593
+
594
+ const HookRegistrySchema = z.object({
595
+ hooks: z.instanceof(Map),
596
+ triggerIndex: z.instanceof(Map)
597
+ });
598
+ function createHookRegistry() {
599
+ return {
600
+ hooks: /* @__PURE__ */ new Map(),
601
+ triggerIndex: /* @__PURE__ */ new Map()
602
+ };
603
+ }
604
+ function registerHook(registry, hook) {
605
+ const validatedRegistry = HookRegistrySchema.parse(registry);
606
+ const validatedHook = HookSchema.parse(hook);
607
+ if (validatedRegistry.hooks.has(validatedHook.name)) {
608
+ throw new Error(`Hook already registered: ${validatedHook.name}`);
609
+ }
610
+ validatedRegistry.hooks.set(validatedHook.name, validatedHook);
611
+ if (!validatedRegistry.triggerIndex.has(validatedHook.trigger)) {
612
+ validatedRegistry.triggerIndex.set(validatedHook.trigger, /* @__PURE__ */ new Set());
613
+ }
614
+ validatedRegistry.triggerIndex.get(validatedHook.trigger).add(validatedHook.name);
615
+ }
616
+ function unregisterHook(registry, name) {
617
+ const validatedRegistry = HookRegistrySchema.parse(registry);
618
+ const hook = validatedRegistry.hooks.get(name);
619
+ if (!hook) {
620
+ return false;
621
+ }
622
+ validatedRegistry.hooks.delete(name);
623
+ const triggerSet = validatedRegistry.triggerIndex.get(hook.trigger);
624
+ if (triggerSet) {
625
+ triggerSet.delete(name);
626
+ if (triggerSet.size === 0) {
627
+ validatedRegistry.triggerIndex.delete(hook.trigger);
628
+ }
629
+ }
630
+ return true;
631
+ }
632
+ function getHook(registry, name) {
633
+ const validatedRegistry = HookRegistrySchema.parse(registry);
634
+ return validatedRegistry.hooks.get(name);
635
+ }
636
+ function listHooks(registry) {
637
+ const validatedRegistry = HookRegistrySchema.parse(registry);
638
+ return Array.from(validatedRegistry.hooks.values());
639
+ }
640
+ function getHooksByTrigger(registry, trigger) {
641
+ const validatedRegistry = HookRegistrySchema.parse(registry);
642
+ const hookNames = validatedRegistry.triggerIndex.get(trigger);
643
+ if (!hookNames) {
644
+ return [];
645
+ }
646
+ const hooks = [];
647
+ for (const name of hookNames) {
648
+ const hook = validatedRegistry.hooks.get(name);
649
+ if (hook) {
650
+ hooks.push(hook);
651
+ }
652
+ }
653
+ return hooks;
654
+ }
655
+ function hasHook(registry, name) {
656
+ const validatedRegistry = HookRegistrySchema.parse(registry);
657
+ return validatedRegistry.hooks.has(name);
658
+ }
659
+ function clearHooks(registry) {
660
+ const validatedRegistry = HookRegistrySchema.parse(registry);
661
+ validatedRegistry.hooks.clear();
662
+ validatedRegistry.triggerIndex.clear();
663
+ }
664
+ function getRegistryStats(registry) {
665
+ const validatedRegistry = HookRegistrySchema.parse(registry);
666
+ const byTrigger = {};
667
+ for (const [trigger, names] of validatedRegistry.triggerIndex) {
668
+ byTrigger[trigger] = names.size;
669
+ }
670
+ return {
671
+ totalHooks: validatedRegistry.hooks.size,
672
+ byTrigger
673
+ };
674
+ }
675
+
676
+ const validateSubjectIRI = defineHook({
677
+ name: "validate-subject-iri",
678
+ trigger: "before-add",
679
+ validate: (quad) => {
680
+ return quad.subject.termType === "NamedNode";
681
+ },
682
+ metadata: {
683
+ description: "Validates that quad subject is a Named Node (IRI)"
684
+ }
685
+ });
686
+ const validatePredicateIRI = defineHook({
687
+ name: "validate-predicate-iri",
688
+ trigger: "before-add",
689
+ validate: (quad) => {
690
+ return quad.predicate.termType === "NamedNode";
691
+ },
692
+ metadata: {
693
+ description: "Validates that quad predicate is a Named Node (IRI)"
694
+ }
695
+ });
696
+ const validateObjectLiteral = defineHook({
697
+ name: "validate-object-literal",
698
+ trigger: "before-add",
699
+ validate: (quad) => {
700
+ return quad.object.termType === "Literal";
701
+ },
702
+ metadata: {
703
+ description: "Validates that quad object is a Literal"
704
+ }
705
+ });
706
+ const validateIRIFormat = defineHook({
707
+ name: "validate-iri-format",
708
+ trigger: "before-add",
709
+ validate: (quad) => {
710
+ const validateIRI = (term) => {
711
+ if (term.termType !== "NamedNode") {
712
+ return true;
713
+ }
714
+ try {
715
+ new URL(term.value);
716
+ return true;
717
+ } catch {
718
+ return false;
719
+ }
720
+ };
721
+ return validateIRI(quad.subject) && validateIRI(quad.predicate) && validateIRI(quad.object);
722
+ },
723
+ metadata: {
724
+ description: "Validates that IRI values are well-formed URLs"
725
+ }
726
+ });
727
+ const validateLanguageTag = defineHook({
728
+ name: "validate-language-tag",
729
+ trigger: "before-add",
730
+ validate: (quad) => {
731
+ if (quad.object.termType !== "Literal") {
732
+ return true;
733
+ }
734
+ return quad.object.language !== void 0 && quad.object.language !== "";
735
+ },
736
+ metadata: {
737
+ description: "Validates that literal objects have language tags"
738
+ }
739
+ });
740
+ const rejectBlankNodes = defineHook({
741
+ name: "reject-blank-nodes",
742
+ trigger: "before-add",
743
+ validate: (quad) => {
744
+ return quad.subject.termType !== "BlankNode" && quad.object.termType !== "BlankNode";
745
+ },
746
+ metadata: {
747
+ description: "Rejects quads containing blank nodes"
748
+ }
749
+ });
750
+ const normalizeNamespace = defineHook({
751
+ name: "normalize-namespace",
752
+ trigger: "before-add",
753
+ transform: (quad) => {
754
+ return quad;
755
+ },
756
+ metadata: {
757
+ description: "Normalizes namespace prefixes to full IRIs"
758
+ }
759
+ });
760
+ const normalizeLanguageTag = defineHook({
761
+ name: "normalize-language-tag",
762
+ trigger: "before-add",
763
+ transform: (quad) => {
764
+ if (quad.object.termType !== "Literal" || !quad.object.language) {
765
+ return quad;
766
+ }
767
+ return dataFactory.quad(
768
+ quad.subject,
769
+ quad.predicate,
770
+ dataFactory.literal(quad.object.value, quad.object.language.toLowerCase()),
771
+ quad.graph
772
+ );
773
+ },
774
+ metadata: {
775
+ description: "Normalizes language tags to lowercase"
776
+ }
777
+ });
778
+ const trimLiterals = defineHook({
779
+ name: "trim-literals",
780
+ trigger: "before-add",
781
+ transform: (quad) => {
782
+ if (quad.object.termType !== "Literal") {
783
+ return quad;
784
+ }
785
+ return dataFactory.quad(
786
+ quad.subject,
787
+ quad.predicate,
788
+ dataFactory.literal(quad.object.value.trim(), quad.object.language || quad.object.datatype),
789
+ quad.graph
790
+ );
791
+ },
792
+ metadata: {
793
+ description: "Trims whitespace from literal values"
794
+ }
795
+ });
796
+ const standardValidation = defineHook({
797
+ name: "standard-validation",
798
+ trigger: "before-add",
799
+ validate: (quad) => {
800
+ return quad.predicate.termType === "NamedNode" && (quad.subject.termType === "NamedNode" || quad.subject.termType === "BlankNode");
801
+ },
802
+ metadata: {
803
+ description: "Standard RDF validation rules"
804
+ }
805
+ });
806
+ const normalizeLanguageTagPooled = defineHook({
807
+ name: "normalize-language-tag-pooled",
808
+ trigger: "before-add",
809
+ transform: (quad) => {
810
+ const isLiteral = quad.object.termType === "Literal";
811
+ const hasLanguage = isLiteral && quad.object.language;
812
+ if (!hasLanguage) return quad;
813
+ return quadPool.acquire(
814
+ quad.subject,
815
+ quad.predicate,
816
+ dataFactory.literal(quad.object.value, quad.object.language.toLowerCase()),
817
+ quad.graph
818
+ );
819
+ },
820
+ metadata: {
821
+ description: "Zero-allocation language tag normalization using quad pool",
822
+ pooled: true
823
+ }
824
+ });
825
+ const trimLiteralsPooled = defineHook({
826
+ name: "trim-literals-pooled",
827
+ trigger: "before-add",
828
+ transform: (quad) => {
829
+ if (quad.object.termType !== "Literal") return quad;
830
+ const trimmed = quad.object.value.trim();
831
+ if (trimmed === quad.object.value) return quad;
832
+ return quadPool.acquire(
833
+ quad.subject,
834
+ quad.predicate,
835
+ dataFactory.literal(trimmed, quad.object.language || quad.object.datatype),
836
+ quad.graph
837
+ );
838
+ },
839
+ metadata: {
840
+ description: "Zero-allocation literal trimming using quad pool",
841
+ pooled: true
842
+ }
843
+ });
844
+ const builtinHooks = {
845
+ // Validation
846
+ validateSubjectIRI,
847
+ validatePredicateIRI,
848
+ validateObjectLiteral,
849
+ validateIRIFormat,
850
+ validateLanguageTag,
851
+ rejectBlankNodes,
852
+ // Transformation
853
+ normalizeNamespace,
854
+ normalizeLanguageTag,
855
+ trimLiterals,
856
+ // Pooled variants (zero-allocation)
857
+ normalizeLanguageTagPooled,
858
+ trimLiteralsPooled,
859
+ // Composite
860
+ standardValidation
861
+ };
862
+
863
+ class KnowledgeHookManager {
864
+ /**
865
+ * @private
866
+ * @type {import('./hook-management.mjs').HookRegistry}
867
+ */
868
+ #registry;
869
+ /**
870
+ * POKA-YOKE: Recursive execution guard (RPN 128 → 0)
871
+ * @private
872
+ * @type {number}
873
+ */
874
+ #executionDepth = 0;
875
+ /**
876
+ * Maximum allowed execution depth
877
+ * @private
878
+ * @type {number}
879
+ */
880
+ #maxExecutionDepth = 3;
881
+ /**
882
+ * Create a new KnowledgeHookManager
883
+ *
884
+ * @param {Object} [options] - Configuration options
885
+ * @param {boolean} [options.includeBuiltins=false] - Include built-in hooks
886
+ * @param {number} [options.maxExecutionDepth=3] - Maximum recursion depth (1-10)
887
+ */
888
+ constructor(options = {}) {
889
+ this.#registry = createHookRegistry();
890
+ if (options.maxExecutionDepth !== void 0) {
891
+ if (options.maxExecutionDepth < 1 || options.maxExecutionDepth > 10) {
892
+ throw new Error(
893
+ `[POKA-YOKE] maxExecutionDepth must be between 1 and 10, got ${options.maxExecutionDepth}`
894
+ );
895
+ }
896
+ this.#maxExecutionDepth = options.maxExecutionDepth;
897
+ }
898
+ if (options.includeBuiltins) {
899
+ for (const hook of Object.values(builtinHooks)) {
900
+ registerHook(this.#registry, hook);
901
+ }
902
+ }
903
+ }
904
+ /**
905
+ * Get current execution depth
906
+ * @returns {number}
907
+ */
908
+ getExecutionDepth() {
909
+ return this.#executionDepth;
910
+ }
911
+ /**
912
+ * Check if currently executing hooks
913
+ * @returns {boolean}
914
+ */
915
+ isExecuting() {
916
+ return this.#executionDepth > 0;
917
+ }
918
+ /**
919
+ * Define and register a hook
920
+ *
921
+ * @param {import('./define-hook.mjs').HookConfig} hookDef - Hook definition
922
+ * @returns {import('./define-hook.mjs').Hook} The defined hook
923
+ */
924
+ define(hookDef) {
925
+ const hook = defineHook(hookDef);
926
+ registerHook(this.#registry, hook);
927
+ return hook;
928
+ }
929
+ /**
930
+ * Register a hook
931
+ *
932
+ * @param {import('./define-hook.mjs').Hook} hook - Hook to register
933
+ * @returns {void}
934
+ */
935
+ registerHook(hook) {
936
+ registerHook(this.#registry, hook);
937
+ }
938
+ /**
939
+ * Unregister a hook
940
+ *
941
+ * @param {string} hookId - ID of hook to unregister
942
+ * @returns {boolean} True if hook was removed
943
+ */
944
+ unregisterHook(hookId) {
945
+ return unregisterHook(this.#registry, hookId);
946
+ }
947
+ /**
948
+ * Get a hook by ID
949
+ *
950
+ * @param {string} hookId - Hook ID
951
+ * @returns {import('./define-hook.mjs').Hook | undefined}
952
+ */
953
+ getHook(hookId) {
954
+ return getHook(this.#registry, hookId);
955
+ }
956
+ /**
957
+ * List all hooks
958
+ *
959
+ * @param {Object} [options] - Filter options
960
+ * @param {string} [options.trigger] - Filter by trigger
961
+ * @param {boolean} [options.enabled] - Filter by enabled status
962
+ * @returns {import('./define-hook.mjs').Hook[]}
963
+ */
964
+ listHooks(options) {
965
+ return listHooks(this.#registry);
966
+ }
967
+ /**
968
+ * Get hooks by trigger
969
+ *
970
+ * @param {string} trigger - Trigger to filter by
971
+ * @returns {import('./define-hook.mjs').Hook[]}
972
+ */
973
+ getHooksByTrigger(trigger) {
974
+ return getHooksByTrigger(this.#registry, trigger);
975
+ }
976
+ /**
977
+ * Check if hook exists
978
+ *
979
+ * @param {string} hookId - Hook ID
980
+ * @returns {boolean}
981
+ */
982
+ hasHook(hookId) {
983
+ return hasHook(this.#registry, hookId);
984
+ }
985
+ /**
986
+ * Clear all hooks
987
+ *
988
+ * @returns {void}
989
+ */
990
+ clearHooks() {
991
+ clearHooks(this.#registry);
992
+ }
993
+ /**
994
+ * Get registry statistics
995
+ *
996
+ * @returns {{ total: number, enabled: number, disabled: number, byTrigger: Record<string, number> }}
997
+ */
998
+ getStats() {
999
+ return getRegistryStats(this.#registry);
1000
+ }
1001
+ /**
1002
+ * Execute a specific hook
1003
+ *
1004
+ * @param {string} hookId - Hook ID
1005
+ * @param {*} data - Data to process
1006
+ * @param {*} context - Execution context
1007
+ * @returns {Promise<import('./hook-executor.mjs').HookResult>}
1008
+ */
1009
+ async executeHook(hookId, data, context) {
1010
+ const hook = this.getHook(hookId);
1011
+ if (!hook) {
1012
+ throw new Error(`Hook not found: ${hookId}`);
1013
+ }
1014
+ return executeHook(hook, data, context);
1015
+ }
1016
+ /**
1017
+ * Execute hooks in chain
1018
+ *
1019
+ * @param {import('./define-hook.mjs').Hook[]} hooks - Hooks to execute
1020
+ * @param {*} data - Initial data
1021
+ * @param {*} context - Execution context
1022
+ * @returns {Promise<import('./hook-executor.mjs').ChainResult>}
1023
+ */
1024
+ async executeChain(hooks, data, context) {
1025
+ return executeHookChain(hooks, data);
1026
+ }
1027
+ /**
1028
+ * Execute hooks by trigger
1029
+ *
1030
+ * @param {string} trigger - Trigger to execute
1031
+ * @param {*} data - Data to process
1032
+ * @param {*} context - Execution context
1033
+ * @returns {Promise<import('./hook-executor.mjs').ChainResult>}
1034
+ */
1035
+ async executeByTrigger(trigger, data, context) {
1036
+ if (this.#executionDepth >= this.#maxExecutionDepth) {
1037
+ const error = new Error(
1038
+ `[POKA-YOKE] Recursive hook execution detected (depth: ${this.#executionDepth}, max: ${this.#maxExecutionDepth}). Trigger: ${trigger}`
1039
+ );
1040
+ error.code = "RECURSIVE_HOOK_EXECUTION";
1041
+ throw error;
1042
+ }
1043
+ this.#executionDepth++;
1044
+ try {
1045
+ const hooks = this.listHooks();
1046
+ return executeHooksByTrigger(hooks, trigger, data, context);
1047
+ } finally {
1048
+ this.#executionDepth--;
1049
+ }
1050
+ }
1051
+ /**
1052
+ * Check if data would pass hooks
1053
+ *
1054
+ * @param {string} trigger - Trigger to check
1055
+ * @param {*} data - Data to validate
1056
+ * @param {*} context - Execution context
1057
+ * @returns {Promise<boolean>}
1058
+ */
1059
+ async wouldPass(trigger, data, context) {
1060
+ const hooks = this.getHooksByTrigger(trigger);
1061
+ return wouldPassHooks(hooks, data);
1062
+ }
1063
+ /**
1064
+ * Get built-in hooks
1065
+ *
1066
+ * @returns {import('./define-hook.mjs').Hook[]}
1067
+ */
1068
+ static getBuiltinHooks() {
1069
+ return Object.values(builtinHooks);
1070
+ }
1071
+ }
1072
+
1073
+ const ScheduleConfigSchema = z.object({
1074
+ id: z.string().min(1),
1075
+ hookId: z.string().min(1),
1076
+ type: z.enum(["cron", "interval", "idle", "startup"]),
1077
+ expression: z.string().optional(),
1078
+ // Cron expression
1079
+ // POKA-YOKE: Interval bounds validation (RPN 168 → 0)
1080
+ // Min 10ms prevents CPU thrashing, max 24h prevents integer overflow
1081
+ intervalMs: z.number().positive().min(10, "Interval must be at least 10ms to prevent CPU thrashing").max(864e5, "Interval cannot exceed 24 hours (86400000ms)").optional(),
1082
+ idleTimeoutMs: z.number().positive().min(100, "Idle timeout must be at least 100ms").max(36e5, "Idle timeout cannot exceed 1 hour").optional(),
1083
+ enabled: z.boolean().default(true),
1084
+ maxRuns: z.number().positive().optional(),
1085
+ // Max executions
1086
+ metadata: z.record(z.any()).optional()
1087
+ });
1088
+ class HookScheduler {
1089
+ /**
1090
+ * Create a new hook scheduler
1091
+ *
1092
+ * @param {object} options - Scheduler options
1093
+ * @param {Function} options.executeHook - Hook execution function
1094
+ * @param {number} options.tickInterval - Scheduler tick interval (default: 1000ms)
1095
+ */
1096
+ constructor(options = {}) {
1097
+ this.schedules = /* @__PURE__ */ new Map();
1098
+ this.executeHook = options.executeHook || (async () => {
1099
+ });
1100
+ this.tickInterval = options.tickInterval || 1e3;
1101
+ this.ticker = null;
1102
+ this.running = false;
1103
+ this.lastTick = null;
1104
+ this.idleThreshold = options.idleThreshold || 5e3;
1105
+ this.idleStart = Date.now();
1106
+ this.startupQueue = [];
1107
+ }
1108
+ /**
1109
+ * Register a scheduled hook
1110
+ *
1111
+ * @param {import('./define-hook.mjs').Hook} hook - Hook to schedule
1112
+ * @param {object} config - Schedule configuration
1113
+ * @returns {ScheduledHook} - Registered scheduled hook
1114
+ */
1115
+ register(hook, config) {
1116
+ const validConfig = ScheduleConfigSchema.parse(config);
1117
+ const scheduled = {
1118
+ id: validConfig.id,
1119
+ hook,
1120
+ type: validConfig.type,
1121
+ expression: validConfig.expression,
1122
+ interval: validConfig.intervalMs,
1123
+ idleTimeout: validConfig.idleTimeoutMs,
1124
+ enabled: validConfig.enabled,
1125
+ lastRun: null,
1126
+ nextRun: this._calculateNextRun(validConfig),
1127
+ runCount: 0,
1128
+ maxRuns: validConfig.maxRuns,
1129
+ metadata: validConfig.metadata || {}
1130
+ };
1131
+ this.schedules.set(validConfig.id, scheduled);
1132
+ if (validConfig.type === "startup") {
1133
+ this.startupQueue.push(scheduled);
1134
+ }
1135
+ return scheduled;
1136
+ }
1137
+ /**
1138
+ * Unregister a scheduled hook
1139
+ *
1140
+ * @param {string} id - Schedule ID to remove
1141
+ */
1142
+ unregister(id) {
1143
+ this.schedules.delete(id);
1144
+ }
1145
+ /**
1146
+ * Start the scheduler
1147
+ */
1148
+ start() {
1149
+ if (this.running) return;
1150
+ this.running = true;
1151
+ this.lastTick = /* @__PURE__ */ new Date();
1152
+ this._executeStartupHooks();
1153
+ this.ticker = setInterval(() => this._tick(), this.tickInterval);
1154
+ }
1155
+ /**
1156
+ * Stop the scheduler
1157
+ */
1158
+ stop() {
1159
+ if (!this.running) return;
1160
+ this.running = false;
1161
+ if (this.ticker) {
1162
+ clearInterval(this.ticker);
1163
+ this.ticker = null;
1164
+ }
1165
+ }
1166
+ /**
1167
+ * Notify scheduler of activity (resets idle timer)
1168
+ */
1169
+ notifyActivity() {
1170
+ this.idleStart = Date.now();
1171
+ }
1172
+ /**
1173
+ * Get scheduler statistics
1174
+ *
1175
+ * @returns {object} - Scheduler stats
1176
+ */
1177
+ getStats() {
1178
+ const schedules = Array.from(this.schedules.values());
1179
+ return {
1180
+ totalSchedules: schedules.length,
1181
+ enabledSchedules: schedules.filter((s) => s.enabled).length,
1182
+ totalRuns: schedules.reduce((sum, s) => sum + s.runCount, 0),
1183
+ byType: {
1184
+ cron: schedules.filter((s) => s.type === "cron").length,
1185
+ interval: schedules.filter((s) => s.type === "interval").length,
1186
+ idle: schedules.filter((s) => s.type === "idle").length,
1187
+ startup: schedules.filter((s) => s.type === "startup").length
1188
+ },
1189
+ running: this.running,
1190
+ idleSince: this.idleStart
1191
+ };
1192
+ }
1193
+ /**
1194
+ * Execute startup hooks
1195
+ * @private
1196
+ */
1197
+ async _executeStartupHooks() {
1198
+ for (const scheduled of this.startupQueue) {
1199
+ if (scheduled.enabled && scheduled.runCount === 0) {
1200
+ await this._executeScheduled(scheduled);
1201
+ }
1202
+ }
1203
+ this.startupQueue = [];
1204
+ }
1205
+ /**
1206
+ * Scheduler tick - check and execute due hooks
1207
+ * @private
1208
+ */
1209
+ async _tick() {
1210
+ const now = /* @__PURE__ */ new Date();
1211
+ const isIdle = Date.now() - this.idleStart > this.idleThreshold;
1212
+ for (const scheduled of this.schedules.values()) {
1213
+ if (!scheduled.enabled) continue;
1214
+ if (scheduled.maxRuns && scheduled.runCount >= scheduled.maxRuns) continue;
1215
+ let shouldRun = false;
1216
+ switch (scheduled.type) {
1217
+ case "interval":
1218
+ shouldRun = scheduled.nextRun && now >= scheduled.nextRun;
1219
+ break;
1220
+ case "idle":
1221
+ shouldRun = isIdle && (!scheduled.lastRun || Date.now() - scheduled.lastRun.getTime() > (scheduled.idleTimeout || this.idleThreshold));
1222
+ break;
1223
+ case "cron":
1224
+ shouldRun = scheduled.nextRun && now >= scheduled.nextRun;
1225
+ break;
1226
+ }
1227
+ if (shouldRun) {
1228
+ await this._executeScheduled(scheduled);
1229
+ }
1230
+ }
1231
+ this.lastTick = now;
1232
+ }
1233
+ /**
1234
+ * Execute a scheduled hook
1235
+ * POKA-YOKE: Circuit breaker disables after 3 consecutive failures (RPN 432 → 43)
1236
+ * @private
1237
+ */
1238
+ async _executeScheduled(scheduled) {
1239
+ try {
1240
+ scheduled.lastRun = /* @__PURE__ */ new Date();
1241
+ scheduled.runCount++;
1242
+ scheduled.nextRun = this._calculateNextRun(scheduled);
1243
+ await this.executeHook(scheduled.hook, {
1244
+ scheduledId: scheduled.id,
1245
+ runCount: scheduled.runCount,
1246
+ scheduledTime: scheduled.lastRun
1247
+ });
1248
+ scheduled.errorCount = 0;
1249
+ scheduled.lastError = null;
1250
+ } catch (error) {
1251
+ scheduled.lastError = error instanceof Error ? error : new Error(String(error));
1252
+ scheduled.errorCount = (scheduled.errorCount || 0) + 1;
1253
+ if (this.onError) {
1254
+ this.onError({
1255
+ scheduledId: scheduled.id,
1256
+ hookName: scheduled.hook?.name || "unknown",
1257
+ error: scheduled.lastError,
1258
+ errorCount: scheduled.errorCount,
1259
+ timestamp: /* @__PURE__ */ new Date()
1260
+ });
1261
+ }
1262
+ console.error(
1263
+ `[POKA-YOKE] Scheduled hook "${scheduled.id}" failed (attempt ${scheduled.errorCount}/3):`,
1264
+ scheduled.lastError.message
1265
+ );
1266
+ if (scheduled.errorCount >= 3) {
1267
+ scheduled.enabled = false;
1268
+ console.warn(
1269
+ `[POKA-YOKE] Scheduled hook "${scheduled.id}" disabled after 3 consecutive failures. Last error: ${scheduled.lastError.message}. Re-enable with scheduler.enable("${scheduled.id}") after fixing the issue.`
1270
+ );
1271
+ if (this.onCircuitOpen) {
1272
+ this.onCircuitOpen({
1273
+ scheduledId: scheduled.id,
1274
+ hookName: scheduled.hook?.name || "unknown",
1275
+ lastError: scheduled.lastError,
1276
+ totalErrors: scheduled.errorCount,
1277
+ timestamp: /* @__PURE__ */ new Date()
1278
+ });
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+ /**
1284
+ * Re-enable a disabled scheduled hook
1285
+ *
1286
+ * @param {string} id - Schedule ID to enable
1287
+ * @returns {boolean} - True if enabled, false if not found
1288
+ */
1289
+ enable(id) {
1290
+ const scheduled = this.schedules.get(id);
1291
+ if (!scheduled) return false;
1292
+ scheduled.enabled = true;
1293
+ scheduled.errorCount = 0;
1294
+ scheduled.lastError = null;
1295
+ return true;
1296
+ }
1297
+ /**
1298
+ * Calculate next run time for a schedule
1299
+ * @private
1300
+ */
1301
+ _calculateNextRun(config) {
1302
+ const now = /* @__PURE__ */ new Date();
1303
+ switch (config.type) {
1304
+ case "interval":
1305
+ return new Date(now.getTime() + (config.intervalMs || config.interval || 6e4));
1306
+ case "cron":
1307
+ return this._parseCronExpression(config.expression);
1308
+ case "idle":
1309
+ case "startup":
1310
+ return null;
1311
+ // Event-driven, not time-driven
1312
+ default:
1313
+ return null;
1314
+ }
1315
+ }
1316
+ /**
1317
+ * Simple cron expression parser
1318
+ * POKA-YOKE: Strict validation instead of silent fallback (RPN 315 → 0)
1319
+ * Supports intervals in the format star-slash-n (e.g., star-slash-5 = every 5 minutes)
1320
+ * @private
1321
+ */
1322
+ _parseCronExpression(expression) {
1323
+ if (!expression) {
1324
+ throw new Error(
1325
+ 'Cron expression is required for type "cron". Use "*/n" format for intervals (e.g., "*/5" for every 5 minutes).'
1326
+ );
1327
+ }
1328
+ const intervalMatch = expression.match(/^\*\/(\d+)$/);
1329
+ if (intervalMatch) {
1330
+ const minutes = parseInt(intervalMatch[1], 10);
1331
+ if (minutes < 1 || minutes > 1440) {
1332
+ throw new Error(
1333
+ `Invalid cron interval: */[${minutes}]. Value must be between 1 and 1440 minutes (24 hours). Example: "*/5" for every 5 minutes.`
1334
+ );
1335
+ }
1336
+ return new Date(Date.now() + minutes * 60 * 1e3);
1337
+ }
1338
+ throw new Error(
1339
+ `Invalid cron expression: "${expression}". Supported format: "*/n" where n is minutes (1-1440). Example: "*/5" for every 5 minutes, "*/60" for every hour.`
1340
+ );
1341
+ }
1342
+ }
1343
+ function createHookScheduler(options = {}) {
1344
+ return new HookScheduler(options);
1345
+ }
1346
+
1347
+ const QualityGateSchema = z.object({
1348
+ name: z.string().min(1, "Gate name is required"),
1349
+ metric: z.string().min(1, "Metric name is required"),
1350
+ operator: z.enum(["gt", "gte", "lt", "lte", "eq", "neq", "between"]),
1351
+ threshold: z.number().or(z.array(z.number())),
1352
+ severity: z.enum(["critical", "major", "minor"]).default("major"),
1353
+ action: z.enum(["block", "warn", "log"]).default("block")
1354
+ }).refine(
1355
+ (data) => {
1356
+ if (data.operator === "between") {
1357
+ return Array.isArray(data.threshold) && data.threshold.length === 2;
1358
+ }
1359
+ return typeof data.threshold === "number";
1360
+ },
1361
+ {
1362
+ message: "Operator 'between' requires threshold as [min, max] array; other operators require a single number. Example: { operator: 'between', threshold: [10, 90] } or { operator: 'gt', threshold: 50 }"
1363
+ }
1364
+ ).refine(
1365
+ (data) => {
1366
+ if (data.operator === "between" && Array.isArray(data.threshold)) {
1367
+ return data.threshold[0] <= data.threshold[1];
1368
+ }
1369
+ return true;
1370
+ },
1371
+ {
1372
+ message: "For 'between' operator, threshold[0] (min) must be <= threshold[1] (max)"
1373
+ }
1374
+ );
1375
+ const SPCDataPointSchema = z.object({
1376
+ timestamp: z.date().or(z.string().transform((s) => new Date(s))),
1377
+ value: z.number(),
1378
+ subgroup: z.string().optional()
1379
+ });
1380
+ class QualityMetricsCollector {
1381
+ /**
1382
+ * Create a new quality metrics collector
1383
+ *
1384
+ * @param {object} options - Collector options
1385
+ */
1386
+ constructor(options = {}) {
1387
+ this.dataPoints = /* @__PURE__ */ new Map();
1388
+ this.metrics = /* @__PURE__ */ new Map();
1389
+ this.defects = [];
1390
+ this.maxDataPoints = options.maxDataPoints || 1e3;
1391
+ this.qualityGates = /* @__PURE__ */ new Map();
1392
+ this.auditLog = [];
1393
+ this.maxAuditLogSize = options.maxAuditLogSize || 1e4;
1394
+ this.defectCount = 0;
1395
+ this.totalCount = 0;
1396
+ }
1397
+ /**
1398
+ * Record a data point for a metric
1399
+ *
1400
+ * @param {string} metricName - Metric name
1401
+ * @param {number} value - Measured value
1402
+ * @param {object} context - Additional context
1403
+ */
1404
+ record(metricName, value, context = {}) {
1405
+ if (!this.dataPoints.has(metricName)) {
1406
+ this.dataPoints.set(metricName, []);
1407
+ }
1408
+ const points = this.dataPoints.get(metricName);
1409
+ points.push(value);
1410
+ if (points.length > this.maxDataPoints) {
1411
+ points.shift();
1412
+ }
1413
+ this.totalCount++;
1414
+ this._checkControlLimits(metricName, value, context);
1415
+ }
1416
+ /**
1417
+ * Record a defect
1418
+ *
1419
+ * @param {object} defect - Defect information
1420
+ */
1421
+ recordDefect(defect) {
1422
+ const record = {
1423
+ id: `DEF-${Date.now()}-${this.defects.length}`,
1424
+ timestamp: /* @__PURE__ */ new Date(),
1425
+ ...defect
1426
+ };
1427
+ this.defects.push(record);
1428
+ this.defectCount++;
1429
+ this._auditLog("defect-recorded", record);
1430
+ }
1431
+ /**
1432
+ * Register a quality gate
1433
+ *
1434
+ * @param {object} gate - Quality gate configuration
1435
+ */
1436
+ registerQualityGate(gate) {
1437
+ const validated = QualityGateSchema.parse(gate);
1438
+ this.qualityGates.set(validated.name, validated);
1439
+ }
1440
+ /**
1441
+ * Check a value against quality gates
1442
+ *
1443
+ * @param {string} metricName - Metric to check
1444
+ * @param {number} value - Value to check
1445
+ * @returns {{passed: boolean, violations: object[]}} - Check result
1446
+ */
1447
+ checkQualityGates(metricName, value) {
1448
+ const violations = [];
1449
+ for (const gate of this.qualityGates.values()) {
1450
+ if (gate.metric !== metricName) continue;
1451
+ const passed = this._evaluateGate(gate, value);
1452
+ if (!passed) {
1453
+ violations.push({
1454
+ gate: gate.name,
1455
+ metric: metricName,
1456
+ value,
1457
+ threshold: gate.threshold,
1458
+ operator: gate.operator,
1459
+ severity: gate.severity,
1460
+ action: gate.action
1461
+ });
1462
+ }
1463
+ }
1464
+ return {
1465
+ passed: violations.length === 0,
1466
+ violations
1467
+ };
1468
+ }
1469
+ /**
1470
+ * Calculate Statistical Process Control metrics
1471
+ *
1472
+ * @param {string} metricName - Metric name
1473
+ * @returns {object} - SPC metrics (mean, stdDev, UCL, LCL, Cp, Cpk)
1474
+ */
1475
+ calculateSPC(metricName) {
1476
+ const points = this.dataPoints.get(metricName) || [];
1477
+ if (points.length < 2) {
1478
+ return { error: "Insufficient data points" };
1479
+ }
1480
+ const mean = points.reduce((sum, v) => sum + v, 0) / points.length;
1481
+ const variance = points.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / (points.length - 1);
1482
+ const stdDev = Math.sqrt(variance);
1483
+ const ucl = mean + 3 * stdDev;
1484
+ const lcl = mean - 3 * stdDev;
1485
+ const metric = this.metrics.get(metricName);
1486
+ let cp = null;
1487
+ let cpk = null;
1488
+ if (metric && metric.ucl !== void 0 && metric.lcl !== void 0) {
1489
+ const usl = metric.ucl;
1490
+ const lsl = metric.lcl;
1491
+ cp = (usl - lsl) / (6 * stdDev);
1492
+ cpk = Math.min((usl - mean) / (3 * stdDev), (mean - lsl) / (3 * stdDev));
1493
+ }
1494
+ return {
1495
+ mean,
1496
+ stdDev,
1497
+ ucl,
1498
+ lcl,
1499
+ cp,
1500
+ cpk,
1501
+ n: points.length,
1502
+ min: Math.min(...points),
1503
+ max: Math.max(...points)
1504
+ };
1505
+ }
1506
+ /**
1507
+ * Detect statistical outliers (defects)
1508
+ *
1509
+ * @param {string} metricName - Metric name
1510
+ * @param {number} sigmaLevel - Sigma level for detection (default: 3)
1511
+ * @returns {number[]} - Indices of outlier data points
1512
+ */
1513
+ detectOutliers(metricName, sigmaLevel = 3) {
1514
+ const spc = this.calculateSPC(metricName);
1515
+ if (spc.error) return [];
1516
+ const points = this.dataPoints.get(metricName) || [];
1517
+ const threshold = sigmaLevel * spc.stdDev;
1518
+ return points.map((v, i) => ({ value: v, index: i })).filter(({ value }) => Math.abs(value - spc.mean) > threshold).map(({ index }) => index);
1519
+ }
1520
+ /**
1521
+ * Calculate Defects Per Million Opportunities (DPMO)
1522
+ *
1523
+ * @returns {number} - DPMO value
1524
+ */
1525
+ calculateDPMO() {
1526
+ if (this.totalCount === 0) return 0;
1527
+ return this.defectCount / this.totalCount * 1e6;
1528
+ }
1529
+ /**
1530
+ * Calculate Sigma Level
1531
+ *
1532
+ * @returns {number} - Sigma level (higher is better)
1533
+ */
1534
+ calculateSigmaLevel() {
1535
+ const dpmo = this.calculateDPMO();
1536
+ if (dpmo === 0) return 6;
1537
+ if (dpmo <= 3.4) return 6;
1538
+ if (dpmo <= 233) return 5;
1539
+ if (dpmo <= 6210) return 4;
1540
+ if (dpmo <= 66807) return 3;
1541
+ if (dpmo <= 308538) return 2;
1542
+ if (dpmo <= 691462) return 1;
1543
+ return 0;
1544
+ }
1545
+ /**
1546
+ * Generate 5 Whys root cause analysis template
1547
+ *
1548
+ * @param {DefectRecord} defect - Defect to analyze
1549
+ * @returns {object} - 5 Whys template
1550
+ */
1551
+ generateRootCauseTemplate(defect) {
1552
+ return {
1553
+ defectId: defect.id,
1554
+ problem: defect.type,
1555
+ why1: { question: "Why did this happen?", answer: null },
1556
+ why2: { question: "Why did that cause this?", answer: null },
1557
+ why3: { question: "Why was that the case?", answer: null },
1558
+ why4: { question: "Why did that occur?", answer: null },
1559
+ why5: { question: "What is the root cause?", answer: null },
1560
+ rootCause: null,
1561
+ preventiveAction: null,
1562
+ timestamp: /* @__PURE__ */ new Date()
1563
+ };
1564
+ }
1565
+ /**
1566
+ * Register a Kaizen improvement event
1567
+ *
1568
+ * @param {object} event - Kaizen event details
1569
+ */
1570
+ registerKaizenEvent(event) {
1571
+ const record = {
1572
+ id: `KAIZEN-${Date.now()}`,
1573
+ type: "kaizen-event",
1574
+ timestamp: /* @__PURE__ */ new Date(),
1575
+ status: "open",
1576
+ ...event
1577
+ };
1578
+ this._auditLog("kaizen-registered", record);
1579
+ return record;
1580
+ }
1581
+ /**
1582
+ * Get quality summary report
1583
+ *
1584
+ * @returns {object} - Quality summary
1585
+ */
1586
+ getSummary() {
1587
+ return {
1588
+ totalMeasurements: this.totalCount,
1589
+ totalDefects: this.defectCount,
1590
+ dpmo: this.calculateDPMO(),
1591
+ sigmaLevel: this.calculateSigmaLevel(),
1592
+ metricsTracked: this.metrics.size,
1593
+ dataPointsStored: Array.from(this.dataPoints.values()).reduce(
1594
+ (sum, arr) => sum + arr.length,
1595
+ 0
1596
+ ),
1597
+ qualityGatesRegistered: this.qualityGates.size,
1598
+ auditLogEntries: this.auditLog.length,
1599
+ recentDefects: this.defects.slice(-10)
1600
+ };
1601
+ }
1602
+ /**
1603
+ * Export audit trail
1604
+ *
1605
+ * @param {object} options - Export options
1606
+ * @returns {Array} - Audit trail entries
1607
+ */
1608
+ exportAuditTrail(options = {}) {
1609
+ let entries = [...this.auditLog];
1610
+ if (options.startTime) {
1611
+ entries = entries.filter((e) => e.timestamp >= options.startTime);
1612
+ }
1613
+ if (options.endTime) {
1614
+ entries = entries.filter((e) => e.timestamp <= options.endTime);
1615
+ }
1616
+ if (options.type) {
1617
+ entries = entries.filter((e) => e.type === options.type);
1618
+ }
1619
+ return entries;
1620
+ }
1621
+ /**
1622
+ * Check control limits and record violations
1623
+ * @private
1624
+ */
1625
+ _checkControlLimits(metricName, value, context) {
1626
+ const spc = this.calculateSPC(metricName);
1627
+ if (spc.error) return;
1628
+ if (value > spc.ucl || value < spc.lcl) {
1629
+ this.recordDefect({
1630
+ type: "control-limit-violation",
1631
+ source: metricName,
1632
+ severity: "major",
1633
+ context: {
1634
+ value,
1635
+ ucl: spc.ucl,
1636
+ lcl: spc.lcl,
1637
+ mean: spc.mean,
1638
+ ...context
1639
+ }
1640
+ });
1641
+ }
1642
+ }
1643
+ /**
1644
+ * Evaluate a quality gate
1645
+ * @private
1646
+ */
1647
+ _evaluateGate(gate, value) {
1648
+ switch (gate.operator) {
1649
+ case "gt":
1650
+ return value > gate.threshold;
1651
+ case "gte":
1652
+ return value >= gate.threshold;
1653
+ case "lt":
1654
+ return value < gate.threshold;
1655
+ case "lte":
1656
+ return value <= gate.threshold;
1657
+ case "eq":
1658
+ return value === gate.threshold;
1659
+ case "neq":
1660
+ return value !== gate.threshold;
1661
+ case "between":
1662
+ return Array.isArray(gate.threshold) && value >= gate.threshold[0] && value <= gate.threshold[1];
1663
+ default:
1664
+ return true;
1665
+ }
1666
+ }
1667
+ /**
1668
+ * Add entry to audit log
1669
+ * POKA-YOKE: Includes size limit to prevent memory exhaustion (RPN 448 → 45)
1670
+ * @private
1671
+ */
1672
+ _auditLog(type, data) {
1673
+ if (this.auditLog.length >= this.maxAuditLogSize) {
1674
+ const removeCount = Math.max(1, Math.floor(this.maxAuditLogSize * 0.1));
1675
+ this.auditLog.splice(0, removeCount);
1676
+ if (!this._auditEvictionWarned) {
1677
+ console.warn(
1678
+ `[POKA-YOKE] Audit log reached max size (${this.maxAuditLogSize}). Oldest entries will be evicted. Increase maxAuditLogSize if needed.`
1679
+ );
1680
+ this._auditEvictionWarned = true;
1681
+ }
1682
+ }
1683
+ this.auditLog.push({
1684
+ type,
1685
+ timestamp: /* @__PURE__ */ new Date(),
1686
+ data
1687
+ });
1688
+ }
1689
+ }
1690
+ function createQualityHooks(collector) {
1691
+ return {
1692
+ /**
1693
+ * Create a quality gate validation hook
1694
+ */
1695
+ createQualityGateHook: (gateName, metricExtractor) => ({
1696
+ name: `quality-gate-${gateName}`,
1697
+ trigger: "quality-gate",
1698
+ validate: (data) => {
1699
+ const value = metricExtractor(data);
1700
+ const result = collector.checkQualityGates(gateName, value);
1701
+ if (!result.passed) {
1702
+ const criticalViolations = result.violations.filter((v) => v.action === "block");
1703
+ return criticalViolations.length === 0;
1704
+ }
1705
+ return true;
1706
+ },
1707
+ metadata: { gateName }
1708
+ }),
1709
+ /**
1710
+ * Create a defect detection hook
1711
+ */
1712
+ createDefectDetectionHook: (metricName, extractor) => ({
1713
+ name: `defect-detection-${metricName}`,
1714
+ trigger: "defect-detection",
1715
+ validate: (data) => {
1716
+ const value = extractor(data);
1717
+ collector.record(metricName, value);
1718
+ const outliers = collector.detectOutliers(metricName);
1719
+ return outliers.length === 0;
1720
+ },
1721
+ metadata: { metricName }
1722
+ }),
1723
+ /**
1724
+ * Create an audit trail hook
1725
+ */
1726
+ createAuditTrailHook: (operationType) => ({
1727
+ name: `audit-trail-${operationType}`,
1728
+ trigger: "audit-trail",
1729
+ transform: (data) => {
1730
+ collector._auditLog(operationType, { data, timestamp: /* @__PURE__ */ new Date() });
1731
+ return data;
1732
+ },
1733
+ metadata: { operationType }
1734
+ })
1735
+ };
1736
+ }
1737
+
1738
+ export { ChainResultSchema, HookConfigSchema, HookRegistrySchema, HookResultSchema, HookScheduler, HookSchema, HookTriggerSchema, KnowledgeHookManager, QuadPool, QualityGateSchema, QualityMetricsCollector, SPCDataPointSchema, ScheduleConfigSchema, builtinHooks, clearCompiledChainCache, clearHookCaches, clearHooks, compileHookChain, compileValidationOnlyChain, createHookRegistry, createHookScheduler, createPooledTransform, createQualityHooks, defineHook, executeBatch, executeHook, executeHookChain, executeHooksByTrigger, getChainKey, getCompilerStats, getHook, getHookMetadata, getHooksByTrigger, getRegistryStats, hasHook, hasTransformation$1 as hasTransformation, hasValidation$1 as hasValidation, isJitAvailable, isPooledQuad, isValidHook, listHooks, normalizeLanguageTag, normalizeLanguageTagPooled, normalizeNamespace, prewarmHookCache, quadPool, registerHook, rejectBlankNodes, standardValidation, transformBatch, trimLiterals, trimLiteralsPooled, unregisterHook, validateBatch, validateIRIFormat, validateLanguageTag, validateObjectLiteral, validateOnly, validatePredicateIRI, validateSubjectIRI, wouldPassHooks };