@fulmenhq/tsfulmen 0.3.3 → 0.4.0

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 (123) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/README.md +30 -1
  3. package/config/crucible-ts/taxonomy/metrics.yaml +1 -1
  4. package/dist/appidentity/index.js +9 -2209
  5. package/dist/appidentity/index.js.map +1 -1
  6. package/dist/assets/index.d.ts +179 -0
  7. package/dist/assets/index.js +4 -0
  8. package/dist/assets/index.js.map +1 -0
  9. package/dist/bin/prometheus-cli.js +15 -9139
  10. package/dist/bin/prometheus-cli.js.map +1 -1
  11. package/dist/bin/schema-cli.js +4 -6098
  12. package/dist/bin/schema-cli.js.map +1 -1
  13. package/dist/bin/signals-cli.js +15 -1910
  14. package/dist/bin/signals-cli.js.map +1 -1
  15. package/dist/chunk-3HISR2XI.js +36 -0
  16. package/dist/chunk-3HISR2XI.js.map +1 -0
  17. package/dist/chunk-5HZEJGZ5.js +725 -0
  18. package/dist/chunk-5HZEJGZ5.js.map +1 -0
  19. package/dist/chunk-7SVYOPJ3.js +9412 -0
  20. package/dist/chunk-7SVYOPJ3.js.map +1 -0
  21. package/dist/chunk-BCO3FUUO.js +682 -0
  22. package/dist/chunk-BCO3FUUO.js.map +1 -0
  23. package/dist/chunk-BJDL6JEX.js +208 -0
  24. package/dist/chunk-BJDL6JEX.js.map +1 -0
  25. package/dist/chunk-BTUS6PZZ.js +302 -0
  26. package/dist/chunk-BTUS6PZZ.js.map +1 -0
  27. package/dist/chunk-CW4CSNVF.js +410 -0
  28. package/dist/chunk-CW4CSNVF.js.map +1 -0
  29. package/dist/chunk-HIBZ2AY7.js +194 -0
  30. package/dist/chunk-HIBZ2AY7.js.map +1 -0
  31. package/dist/chunk-HS4F3BRR.js +985 -0
  32. package/dist/chunk-HS4F3BRR.js.map +1 -0
  33. package/dist/chunk-HY6JQOFN.js +32 -0
  34. package/dist/chunk-HY6JQOFN.js.map +1 -0
  35. package/dist/chunk-K2FYT43R.js +837 -0
  36. package/dist/chunk-K2FYT43R.js.map +1 -0
  37. package/dist/chunk-K7BMFGQP.js +43 -0
  38. package/dist/chunk-K7BMFGQP.js.map +1 -0
  39. package/dist/chunk-LWAH2UG7.js +103 -0
  40. package/dist/chunk-LWAH2UG7.js.map +1 -0
  41. package/dist/chunk-OHE2AOH4.js +273 -0
  42. package/dist/chunk-OHE2AOH4.js.map +1 -0
  43. package/dist/chunk-P3DNOZOG.js +1488 -0
  44. package/dist/chunk-P3DNOZOG.js.map +1 -0
  45. package/dist/chunk-PQ6TKPUN.js +27 -0
  46. package/dist/chunk-PQ6TKPUN.js.map +1 -0
  47. package/dist/chunk-QCCN6POA.js +211 -0
  48. package/dist/chunk-QCCN6POA.js.map +1 -0
  49. package/dist/chunk-RSLFR4TO.js +631 -0
  50. package/dist/chunk-RSLFR4TO.js.map +1 -0
  51. package/dist/chunk-UP2VWCW5.js +33 -0
  52. package/dist/chunk-UP2VWCW5.js.map +1 -0
  53. package/dist/chunk-W6TQFTTY.js +130 -0
  54. package/dist/chunk-W6TQFTTY.js.map +1 -0
  55. package/dist/chunk-WSMVLWLB.js +269 -0
  56. package/dist/chunk-WSMVLWLB.js.map +1 -0
  57. package/dist/config/index.js +6 -1220
  58. package/dist/config/index.js.map +1 -1
  59. package/dist/crucible/fulpack/index.js +2 -25
  60. package/dist/crucible/fulpack/index.js.map +1 -1
  61. package/dist/crucible/index.js +13 -1129
  62. package/dist/crucible/index.js.map +1 -1
  63. package/dist/docscribe/index.js +2 -680
  64. package/dist/docscribe/index.js.map +1 -1
  65. package/dist/errors/index.js +5 -1831
  66. package/dist/errors/index.js.map +1 -1
  67. package/dist/errors-D7KXV3QW.js +4 -0
  68. package/dist/errors-D7KXV3QW.js.map +1 -0
  69. package/dist/errors-RBJX6ETJ.js +8 -0
  70. package/dist/errors-RBJX6ETJ.js.map +1 -0
  71. package/dist/export-BWRETGYS.js +6 -0
  72. package/dist/export-BWRETGYS.js.map +1 -0
  73. package/dist/foundry/index.js +340 -3949
  74. package/dist/foundry/index.js.map +1 -1
  75. package/dist/foundry/similarity/index.js +3 -134
  76. package/dist/foundry/similarity/index.js.map +1 -1
  77. package/dist/fulencode/index.js +3 -319
  78. package/dist/fulencode/index.js.map +1 -1
  79. package/dist/fulhash/index.js +3 -611
  80. package/dist/fulhash/index.js.map +1 -1
  81. package/dist/fulpack/index.js +3 -1515
  82. package/dist/fulpack/index.js.map +1 -1
  83. package/dist/hash-YOJYVOWH.js +4 -0
  84. package/dist/hash-YOJYVOWH.js.map +1 -0
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.js +14 -4395
  87. package/dist/index.js.map +1 -1
  88. package/dist/loader-H5K7HSO5.js +9 -0
  89. package/dist/loader-H5K7HSO5.js.map +1 -0
  90. package/dist/logging/index.js +2 -723
  91. package/dist/logging/index.js.map +1 -1
  92. package/dist/pathfinder/index.js +18 -2341
  93. package/dist/pathfinder/index.js.map +1 -1
  94. package/dist/prom-client-PNEBGD3G.js +4801 -0
  95. package/dist/prom-client-PNEBGD3G.js.map +1 -0
  96. package/dist/reports/license-inventory.csv +1 -1
  97. package/dist/schema/index.d.ts +19 -6
  98. package/dist/schema/index.js +4 -6148
  99. package/dist/schema/index.js.map +1 -1
  100. package/dist/signals/index.js +8 -2767
  101. package/dist/signals/index.js.map +1 -1
  102. package/dist/similarity/index.js +3 -134
  103. package/dist/similarity/index.js.map +1 -1
  104. package/dist/telemetry/http/index.js +9 -930
  105. package/dist/telemetry/http/index.js.map +1 -1
  106. package/dist/telemetry/index.js +4 -1561
  107. package/dist/telemetry/index.js.map +1 -1
  108. package/dist/telemetry/prometheus/index.js +7 -8422
  109. package/dist/telemetry/prometheus/index.js.map +1 -1
  110. package/dist/validator-U4CKD2Y3.js +6 -0
  111. package/dist/validator-U4CKD2Y3.js.map +1 -0
  112. package/package.json +5 -1
  113. package/schemas/crucible-ts/library/module-manifest/v1.0.0/module-manifest.schema.json +1 -1
  114. package/schemas/crucible-ts/observability/logging/v1.0.0/definitions.schema.json +1 -1
  115. package/schemas/crucible-ts/observability/logging/v1.0.0/log-event.schema.json +13 -13
  116. package/schemas/crucible-ts/observability/logging/v1.0.0/logger-config.schema.json +2 -2
  117. package/schemas/crucible-ts/observability/logging/v1.0.0/logging-policy.schema.json +1 -1
  118. package/schemas/crucible-ts/observability/logging/v1.0.0/middleware-config.schema.json +2 -2
  119. package/schemas/crucible-ts/observability/logging/v1.0.0/severity-filter.schema.json +3 -3
  120. package/schemas/crucible-ts/upstream/3leaps/crucible/PROVENANCE.md +3 -3
  121. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/catalog/classifiers/README.md +1 -1
  122. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/classifiers-framework.md +1 -1
  123. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/data-sensitivity-classification.md +1 -1
@@ -1,2769 +1,10 @@
1
- import { readFile, access } from 'fs/promises';
2
- import { dirname, join, relative, extname } from 'path';
3
- import { parse } from 'yaml';
4
- import addFormats from 'ajv-formats';
5
- import 'child_process';
6
- import { fileURLToPath } from 'url';
7
- import glob from 'fast-glob';
8
- import 'commander';
9
- import Ajv from 'ajv';
10
- import Ajv2019 from 'ajv/dist/2019.js';
11
- import Ajv2020 from 'ajv/dist/2020.js';
12
- import AjvDraft04 from 'ajv-draft-04';
13
-
14
- var __getOwnPropNames = Object.getOwnPropertyNames;
15
- var __esm = (fn, res) => function __init() {
16
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
17
- };
18
-
19
- // src/telemetry/counter.ts
20
- var Counter;
21
- var init_counter = __esm({
22
- "src/telemetry/counter.ts"() {
23
- Counter = class {
24
- constructor(name) {
25
- this.name = name;
26
- }
27
- value = 0;
28
- labeledValues = /* @__PURE__ */ new Map();
29
- /**
30
- * Increment counter by delta (default: 1)
31
- *
32
- * @param delta - Amount to increment (must be non-negative)
33
- * @param labels - Optional label dimensions for this observation
34
- * @throws {Error} If delta is negative
35
- *
36
- * @example
37
- * ```typescript
38
- * counter.inc(); // Increment unlabeled by 1
39
- * counter.inc(5); // Increment unlabeled by 5
40
- * counter.inc(1, { status: '200' }); // Increment labeled instance
41
- * counter.inc(1, { result: 'success' }); // Different label set
42
- * ```
43
- */
44
- inc(delta = 1, labels) {
45
- if (delta < 0) {
46
- throw new Error(`Counter delta must be non-negative, got: ${delta}`);
47
- }
48
- if (labels && Object.keys(labels).length > 0) {
49
- const labelKey = this.serializeLabels(labels);
50
- const current = this.labeledValues.get(labelKey) || 0;
51
- this.labeledValues.set(labelKey, current + delta);
52
- } else {
53
- this.value += delta;
54
- }
55
- }
56
- /**
57
- * Get current counter value (unlabeled)
58
- */
59
- getValue() {
60
- return this.value;
61
- }
62
- /**
63
- * Get all labeled values
64
- * @returns Map of serialized label keys to values
65
- */
66
- getLabeledValues() {
67
- return new Map(this.labeledValues);
68
- }
69
- /**
70
- * Get value for specific label combination
71
- */
72
- getValueForLabels(labels) {
73
- const labelKey = this.serializeLabels(labels);
74
- return this.labeledValues.get(labelKey) || 0;
75
- }
76
- /**
77
- * Reset counter to zero (all label combinations)
78
- */
79
- reset() {
80
- this.value = 0;
81
- this.labeledValues.clear();
82
- }
83
- /**
84
- * Serialize labels to deterministic string key
85
- * Format: key1=value1,key2=value2 (sorted by key)
86
- */
87
- serializeLabels(labels) {
88
- return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
89
- }
90
- };
91
- }
92
- });
93
-
94
- // src/telemetry/gauge.ts
95
- var Gauge;
96
- var init_gauge = __esm({
97
- "src/telemetry/gauge.ts"() {
98
- Gauge = class {
99
- constructor(name) {
100
- this.name = name;
101
- }
102
- value = 0;
103
- labeledValues = /* @__PURE__ */ new Map();
104
- /**
105
- * Set gauge to specific value
106
- *
107
- * @param value - New gauge value (can be any number, including negative)
108
- * @param labels - Optional label dimensions for this observation
109
- *
110
- * @example
111
- * ```typescript
112
- * gauge.set(42); // Set unlabeled to 42
113
- * gauge.set(-10); // Negative values allowed
114
- * gauge.set(1, { phase: 'collect' }); // Set labeled instance
115
- * ```
116
- */
117
- set(value, labels) {
118
- if (labels && Object.keys(labels).length > 0) {
119
- const labelKey = this.serializeLabels(labels);
120
- this.labeledValues.set(labelKey, value);
121
- } else {
122
- this.value = value;
123
- }
124
- }
125
- /**
126
- * Increment gauge by delta (default: 1)
127
- *
128
- * @param delta - Amount to increment (can be negative)
129
- * @param labels - Optional label dimensions for this observation
130
- */
131
- inc(delta = 1, labels) {
132
- if (labels && Object.keys(labels).length > 0) {
133
- const labelKey = this.serializeLabels(labels);
134
- const current = this.labeledValues.get(labelKey) || 0;
135
- this.labeledValues.set(labelKey, current + delta);
136
- } else {
137
- this.value += delta;
138
- }
139
- }
140
- /**
141
- * Decrement gauge by delta (default: 1)
142
- *
143
- * @param delta - Amount to decrement (can be negative)
144
- * @param labels - Optional label dimensions for this observation
145
- */
146
- dec(delta = 1, labels) {
147
- if (labels && Object.keys(labels).length > 0) {
148
- const labelKey = this.serializeLabels(labels);
149
- const current = this.labeledValues.get(labelKey) || 0;
150
- this.labeledValues.set(labelKey, current - delta);
151
- } else {
152
- this.value -= delta;
153
- }
154
- }
155
- /**
156
- * Get current gauge value (unlabeled)
157
- */
158
- getValue() {
159
- return this.value;
160
- }
161
- /**
162
- * Get all labeled values
163
- * @returns Map of serialized label keys to values
164
- */
165
- getLabeledValues() {
166
- return new Map(this.labeledValues);
167
- }
168
- /**
169
- * Get value for specific label combination
170
- */
171
- getValueForLabels(labels) {
172
- const labelKey = this.serializeLabels(labels);
173
- return this.labeledValues.get(labelKey) || 0;
174
- }
175
- /**
176
- * Reset gauge to zero (all label combinations)
177
- */
178
- reset() {
179
- this.value = 0;
180
- this.labeledValues.clear();
181
- }
182
- /**
183
- * Serialize labels to deterministic string key
184
- * Format: key1=value1,key2=value2 (sorted by key)
185
- */
186
- serializeLabels(labels) {
187
- return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
188
- }
189
- };
190
- }
191
- });
192
- async function getDefaultUnit(name) {
193
- return TaxonomyLoader.getInstance().getDefaultUnit(name);
194
- }
195
- var DEFAULT_MS_BUCKETS, TaxonomyLoader;
196
- var init_taxonomy = __esm({
197
- "src/telemetry/taxonomy.ts"() {
198
- DEFAULT_MS_BUCKETS = [1, 5, 10, 50, 100, 500, 1e3, 5e3, 1e4];
199
- TaxonomyLoader = class _TaxonomyLoader {
200
- static instance;
201
- taxonomy = null;
202
- loadPromise = null;
203
- loadError = null;
204
- constructor() {
205
- }
206
- /**
207
- * Get singleton instance
208
- */
209
- static getInstance() {
210
- if (!_TaxonomyLoader.instance) {
211
- _TaxonomyLoader.instance = new _TaxonomyLoader();
212
- }
213
- return _TaxonomyLoader.instance;
214
- }
215
- /**
216
- * Load taxonomy from YAML file
217
- */
218
- async load() {
219
- if (this.taxonomy !== null) {
220
- return this.taxonomy;
221
- }
222
- if (this.loadError !== null) {
223
- throw this.loadError;
224
- }
225
- if (this.loadPromise) {
226
- return this.loadPromise;
227
- }
228
- this.loadPromise = (async () => {
229
- try {
230
- const taxonomyPath = join(
231
- __dirname,
232
- "..",
233
- "..",
234
- "config",
235
- "crucible-ts",
236
- "taxonomy",
237
- "metrics.yaml"
238
- );
239
- const content = await readFile(taxonomyPath, "utf-8");
240
- this.taxonomy = parse(content);
241
- return this.taxonomy;
242
- } catch (err) {
243
- this.loadError = err instanceof Error ? err : new Error(String(err));
244
- throw new Error(`Failed to load metrics taxonomy: ${this.loadError.message}`);
245
- }
246
- })();
247
- return this.loadPromise;
248
- }
249
- /**
250
- * Get taxonomy (async)
251
- */
252
- async getTaxonomy() {
253
- return this.load();
254
- }
255
- /**
256
- * Get metric definition by name
257
- */
258
- async getMetric(name) {
259
- const taxonomy = await this.load();
260
- return taxonomy.metrics.find((m) => m.name === name);
261
- }
262
- /**
263
- * Get default unit for metric
264
- */
265
- async getDefaultUnit(name) {
266
- const metric = await this.getMetric(name);
267
- return metric?.unit;
268
- }
269
- /**
270
- * Get default histogram buckets for metric
271
- * Returns ADR-0007 buckets for _ms metrics, undefined for others
272
- */
273
- async getDefaultBuckets(name) {
274
- if (name.endsWith("_ms")) {
275
- const taxonomy = await this.load();
276
- return taxonomy.defaults.histogram_buckets.ms_metrics;
277
- }
278
- return void 0;
279
- }
280
- /**
281
- * Check if metric name is valid (exists in taxonomy)
282
- */
283
- async isValidMetricName(name) {
284
- try {
285
- const taxonomy = await this.load();
286
- return taxonomy.metrics.some((m) => m.name === name);
287
- } catch {
288
- return false;
289
- }
290
- }
291
- /**
292
- * Reset loader state (for testing)
293
- * @internal
294
- */
295
- static _reset() {
296
- _TaxonomyLoader.instance = new _TaxonomyLoader();
297
- }
298
- };
299
- }
300
- });
301
-
302
- // src/telemetry/histogram.ts
303
- var Histogram;
304
- var init_histogram = __esm({
305
- "src/telemetry/histogram.ts"() {
306
- init_taxonomy();
307
- Histogram = class {
308
- constructor(name, options) {
309
- this.name = name;
310
- if (options?.buckets) {
311
- this.buckets = [...options.buckets].sort((a, b) => a - b);
312
- } else if (name.endsWith("_ms") || name.endsWith("_seconds")) {
313
- this.buckets = [...DEFAULT_MS_BUCKETS];
314
- } else {
315
- this.buckets = [];
316
- }
317
- for (const bucket of this.buckets) {
318
- this.bucketCounts.set(bucket, 0);
319
- }
320
- }
321
- count = 0;
322
- sum = 0;
323
- bucketCounts = /* @__PURE__ */ new Map();
324
- labeledStates = /* @__PURE__ */ new Map();
325
- buckets;
326
- /**
327
- * Record an observation
328
- *
329
- * @param value - Value to observe (typically a duration in ms or seconds)
330
- * @param labels - Optional label dimensions for this observation
331
- *
332
- * @example
333
- * ```typescript
334
- * const start = performance.now();
335
- * // ... operation ...
336
- * histogram.observe(performance.now() - start);
337
- * histogram.observe(duration, { phase: 'collect', result: 'success' });
338
- * ```
339
- */
340
- observe(value, labels) {
341
- if (labels && Object.keys(labels).length > 0) {
342
- const labelKey = this.serializeLabels(labels);
343
- let state = this.labeledStates.get(labelKey);
344
- if (!state) {
345
- state = {
346
- count: 0,
347
- sum: 0,
348
- bucketCounts: /* @__PURE__ */ new Map()
349
- };
350
- for (const bucket of this.buckets) {
351
- state.bucketCounts.set(bucket, 0);
352
- }
353
- this.labeledStates.set(labelKey, state);
354
- }
355
- state.count++;
356
- state.sum += value;
357
- for (const bucket of this.buckets) {
358
- if (value <= bucket) {
359
- state.bucketCounts.set(bucket, (state.bucketCounts.get(bucket) || 0) + 1);
360
- }
361
- }
362
- } else {
363
- this.count++;
364
- this.sum += value;
365
- for (const bucket of this.buckets) {
366
- if (value <= bucket) {
367
- this.bucketCounts.set(bucket, (this.bucketCounts.get(bucket) || 0) + 1);
368
- }
369
- }
370
- }
371
- }
372
- /**
373
- * Get histogram summary
374
- *
375
- * Returns OTLP-compatible histogram summary with cumulative bucket counts.
376
- */
377
- getSummary() {
378
- const buckets = this.buckets.map((le) => ({
379
- le,
380
- count: this.bucketCounts.get(le) || 0
381
- }));
382
- return {
383
- count: this.count,
384
- sum: this.sum,
385
- buckets
386
- };
387
- }
388
- /**
389
- * Get current observation count
390
- */
391
- getCount() {
392
- return this.count;
393
- }
394
- /**
395
- * Get sum of all observed values
396
- */
397
- getSum() {
398
- return this.sum;
399
- }
400
- /**
401
- * Get average of observed values
402
- */
403
- getAverage() {
404
- return this.count > 0 ? this.sum / this.count : 0;
405
- }
406
- /**
407
- * Get all labeled summaries
408
- * @returns Map of serialized label keys to histogram summaries
409
- */
410
- getLabeledSummaries() {
411
- const summaries = /* @__PURE__ */ new Map();
412
- for (const [labelKey, state] of this.labeledStates) {
413
- const buckets = this.buckets.map((le) => ({
414
- le,
415
- count: state.bucketCounts.get(le) || 0
416
- }));
417
- summaries.set(labelKey, {
418
- count: state.count,
419
- sum: state.sum,
420
- buckets
421
- });
422
- }
423
- return summaries;
424
- }
425
- /**
426
- * Get summary for specific label combination
427
- */
428
- getSummaryForLabels(labels) {
429
- const labelKey = this.serializeLabels(labels);
430
- const state = this.labeledStates.get(labelKey);
431
- if (!state) {
432
- return null;
433
- }
434
- const buckets = this.buckets.map((le) => ({
435
- le,
436
- count: state.bucketCounts.get(le) || 0
437
- }));
438
- return {
439
- count: state.count,
440
- sum: state.sum,
441
- buckets
442
- };
443
- }
444
- /**
445
- * Reset histogram to initial state (all label combinations)
446
- */
447
- reset() {
448
- this.count = 0;
449
- this.sum = 0;
450
- for (const bucket of this.buckets) {
451
- this.bucketCounts.set(bucket, 0);
452
- }
453
- this.labeledStates.clear();
454
- }
455
- /**
456
- * Serialize labels to deterministic string key
457
- * Format: key1=value1,key2=value2 (sorted by key)
458
- */
459
- serializeLabels(labels) {
460
- return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
461
- }
462
- };
463
- }
464
- });
465
-
466
- // src/telemetry/registry.ts
467
- var MetricsRegistry;
468
- var init_registry = __esm({
469
- "src/telemetry/registry.ts"() {
470
- init_counter();
471
- init_gauge();
472
- init_histogram();
473
- init_taxonomy();
474
- MetricsRegistry = class {
475
- counters = /* @__PURE__ */ new Map();
476
- gauges = /* @__PURE__ */ new Map();
477
- histograms = /* @__PURE__ */ new Map();
478
- /**
479
- * Get or create a counter
480
- *
481
- * @param name - Metric name from taxonomy
482
- * @returns Counter instance
483
- *
484
- * @example
485
- * ```typescript
486
- * const counter = registry.counter('schema_validations');
487
- * counter.inc();
488
- * ```
489
- */
490
- counter(name) {
491
- let counter = this.counters.get(name);
492
- if (!counter) {
493
- counter = new Counter(name);
494
- this.counters.set(name, counter);
495
- }
496
- return counter;
497
- }
498
- /**
499
- * Get or create a gauge
500
- *
501
- * @param name - Metric name from taxonomy
502
- * @returns Gauge instance
503
- *
504
- * @example
505
- * ```typescript
506
- * const gauge = registry.gauge('foundry_lookup_count');
507
- * gauge.set(42);
508
- * ```
509
- */
510
- gauge(name) {
511
- let gauge = this.gauges.get(name);
512
- if (!gauge) {
513
- gauge = new Gauge(name);
514
- this.gauges.set(name, gauge);
515
- }
516
- return gauge;
517
- }
518
- /**
519
- * Get or create a histogram
520
- *
521
- * @param name - Metric name from taxonomy
522
- * @param options - Optional histogram options
523
- * @returns Histogram instance
524
- *
525
- * @example
526
- * ```typescript
527
- * // Auto-applies ADR-0007 buckets for _ms metrics
528
- * const histogram = registry.histogram('config_load_ms');
529
- * histogram.observe(42.5);
530
- *
531
- * // Custom buckets
532
- * const custom = registry.histogram('custom_metric', {
533
- * buckets: [10, 50, 100, 500, 1000]
534
- * });
535
- * ```
536
- */
537
- histogram(name, options) {
538
- let histogram = this.histograms.get(name);
539
- if (!histogram) {
540
- histogram = new Histogram(name, options);
541
- this.histograms.set(name, histogram);
542
- }
543
- return histogram;
544
- }
545
- /**
546
- * Export all metrics as events
547
- *
548
- * Returns array of schema-compliant MetricsEvent objects.
549
- * Does not clear metrics (use flush() to clear after export).
550
- *
551
- * @returns Promise resolving to array of metrics events
552
- *
553
- * @example
554
- * ```typescript
555
- * const events = await registry.export();
556
- * console.log(JSON.stringify(events, null, 2));
557
- * ```
558
- */
559
- async export() {
560
- const events = [];
561
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
562
- for (const [name, counter] of this.counters) {
563
- const unit = await getDefaultUnit(name);
564
- events.push({
565
- timestamp,
566
- name,
567
- value: counter.getValue(),
568
- unit
569
- });
570
- for (const [labelKey, value] of counter.getLabeledValues()) {
571
- if (value > 0) {
572
- const tags = this.deserializeLabels(labelKey);
573
- events.push({
574
- timestamp,
575
- name,
576
- value,
577
- tags,
578
- unit
579
- });
580
- }
581
- }
582
- }
583
- for (const [name, gauge] of this.gauges) {
584
- const unit = await getDefaultUnit(name);
585
- events.push({
586
- timestamp,
587
- name,
588
- value: gauge.getValue(),
589
- unit
590
- });
591
- for (const [labelKey, value] of gauge.getLabeledValues()) {
592
- const tags = this.deserializeLabels(labelKey);
593
- events.push({
594
- timestamp,
595
- name,
596
- value,
597
- tags,
598
- unit
599
- });
600
- }
601
- }
602
- for (const [name, histogram] of this.histograms) {
603
- const unit = await getDefaultUnit(name);
604
- events.push({
605
- timestamp,
606
- name,
607
- value: histogram.getSummary(),
608
- unit
609
- });
610
- for (const [labelKey, summary] of histogram.getLabeledSummaries()) {
611
- if (summary.count > 0) {
612
- const tags = this.deserializeLabels(labelKey);
613
- events.push({
614
- timestamp,
615
- name,
616
- value: summary,
617
- tags,
618
- unit
619
- });
620
- }
621
- }
622
- }
623
- return events;
624
- }
625
- /**
626
- * Deserialize label key back to tags object
627
- * Format: key1=value1,key2=value2 → {key1: "value1", key2: "value2"}
628
- */
629
- deserializeLabels(labelKey) {
630
- if (!labelKey) {
631
- return {};
632
- }
633
- const tags = {};
634
- for (const pair of labelKey.split(",")) {
635
- const [key, value] = pair.split("=");
636
- if (key && value) {
637
- tags[key] = value;
638
- }
639
- }
640
- return tags;
641
- }
642
- /**
643
- * Export and clear all metrics
644
- *
645
- * Exports metrics as events, optionally emits them via logger,
646
- * then resets all metrics to zero.
647
- *
648
- * @param options - Flush options
649
- * @returns Promise resolving to array of exported events
650
- *
651
- * @example
652
- * ```typescript
653
- * // Export and clear
654
- * const events = await registry.flush();
655
- *
656
- * // Export, emit to logger, and clear
657
- * const events = await registry.flush({
658
- * emit: (events) => console.log(JSON.stringify(events))
659
- * });
660
- * ```
661
- */
662
- async flush(options) {
663
- const events = await this.export();
664
- try {
665
- if (options?.emit) {
666
- options.emit(events);
667
- }
668
- } finally {
669
- this.clear();
670
- }
671
- return events;
672
- }
673
- /**
674
- * Clear all metrics (reset to zero)
675
- *
676
- * Resets all counters, gauges, and histograms to their initial state.
677
- */
678
- clear() {
679
- for (const counter of this.counters.values()) {
680
- counter.reset();
681
- }
682
- for (const gauge of this.gauges.values()) {
683
- gauge.reset();
684
- }
685
- for (const histogram of this.histograms.values()) {
686
- histogram.reset();
687
- }
688
- }
689
- /**
690
- * Get all registered metric names
691
- *
692
- * Returns array of all metric names that have been accessed
693
- * (counters, gauges, or histograms).
694
- */
695
- getMetricNames() {
696
- const names = /* @__PURE__ */ new Set();
697
- for (const name of this.counters.keys()) {
698
- names.add(name);
699
- }
700
- for (const name of this.gauges.keys()) {
701
- names.add(name);
702
- }
703
- for (const name of this.histograms.keys()) {
704
- names.add(name);
705
- }
706
- return Array.from(names);
707
- }
708
- /**
709
- * Get total count of registered metrics
710
- */
711
- getMetricCount() {
712
- return this.counters.size + this.gauges.size + this.histograms.size;
713
- }
714
- };
715
- }
716
- });
717
-
718
- // src/telemetry/types.ts
719
- var init_types = __esm({
720
- "src/telemetry/types.ts"() {
721
- }
722
- });
723
- function applyFulmenAjvFormats(ajv, options = {}) {
724
- const mode = options.mode ?? "fast";
725
- const formats = options.formats ?? DEFAULT_FORMATS;
726
- addFormats(ajv, { mode, formats });
727
- return ajv;
728
- }
729
- var DEFAULT_FORMATS;
730
- var init_ajv_formats = __esm({
731
- "src/schema/ajv-formats.ts"() {
732
- DEFAULT_FORMATS = [
733
- "date-time",
734
- "email",
735
- "hostname",
736
- "ipv4",
737
- "ipv6",
738
- "uri",
739
- "uri-reference",
740
- "uuid"
741
- ];
742
- }
743
- });
744
-
745
- // src/schema/errors.ts
746
- var SchemaValidationError;
747
- var init_errors = __esm({
748
- "src/schema/errors.ts"() {
749
- SchemaValidationError = class _SchemaValidationError extends Error {
750
- constructor(message, schemaId, diagnostics = [], source, cause) {
751
- super(message);
752
- this.schemaId = schemaId;
753
- this.diagnostics = diagnostics;
754
- this.source = source;
755
- this.cause = cause;
756
- this.name = "SchemaValidationError";
757
- if (Error.captureStackTrace) {
758
- Error.captureStackTrace(this, _SchemaValidationError);
759
- }
760
- }
761
- /**
762
- * Create error for schema not found
763
- */
764
- static schemaNotFound(schemaId) {
765
- return new _SchemaValidationError(`Schema not found: ${schemaId}`, schemaId);
766
- }
767
- /**
768
- * Create error for invalid schema input
769
- */
770
- static invalidSchemaInput(source, details) {
771
- return new _SchemaValidationError(`Invalid schema input: ${details}`, void 0, [], source);
772
- }
773
- /**
774
- * Create error for validation failure
775
- */
776
- static validationFailed(schemaId, diagnostics, source) {
777
- const errorCount = diagnostics.filter((d) => d.severity === "ERROR").length;
778
- const warningCount = diagnostics.filter((d) => d.severity === "WARN").length;
779
- const message = `Schema validation failed: ${errorCount} error(s), ${warningCount} warning(s)`;
780
- return new _SchemaValidationError(message, schemaId, diagnostics, source);
781
- }
782
- /**
783
- * Create error for goneat binary not found
784
- */
785
- static goneatNotFound(goneatPath) {
786
- const pathInfo = goneatPath ? ` at ${goneatPath}` : "";
787
- return new _SchemaValidationError(
788
- `Goneat binary not found${pathInfo}. Falling back to AJV validation.`
789
- );
790
- }
791
- /**
792
- * Create error for goneat execution failure
793
- */
794
- static goneatExecutionFailed(error) {
795
- return new _SchemaValidationError(
796
- "Goneat execution failed. Falling back to AJV validation.",
797
- void 0,
798
- [],
799
- void 0,
800
- error
801
- );
802
- }
803
- /**
804
- * Create error for empty schema input
805
- */
806
- static emptySchemaInput(source) {
807
- return new _SchemaValidationError("Schema content is empty", void 0, [], source);
808
- }
809
- /**
810
- * Create error for parse failure
811
- */
812
- static parseFailed(source, error) {
813
- return new _SchemaValidationError(
814
- `Failed to parse schema: ${error.message}`,
815
- void 0,
816
- [],
817
- source,
818
- error
819
- );
820
- }
821
- /**
822
- * Create error for encoding failure
823
- */
824
- static encodingFailed(source, error) {
825
- return new _SchemaValidationError(
826
- `Failed to encode schema: ${error.message}`,
827
- void 0,
828
- [],
829
- source,
830
- error
831
- );
832
- }
833
- /**
834
- * Create error for registry operation failure
835
- */
836
- static registryError(operation, details) {
837
- return new _SchemaValidationError(`Schema registry ${operation} failed: ${details}`);
838
- }
839
- /**
840
- * Format error for display
841
- */
842
- format() {
843
- let output = this.message;
844
- if (this.schemaId) {
845
- output += `
846
- Schema ID: ${this.schemaId}`;
847
- }
848
- if (this.diagnostics.length > 0) {
849
- output += "\n\nValidation Issues:";
850
- this.diagnostics.forEach((diag, index) => {
851
- output += `
852
- ${index + 1}. [${diag.severity}] ${diag.message}`;
853
- if (diag.pointer) {
854
- output += ` at ${diag.pointer}`;
855
- }
856
- if (diag.keyword) {
857
- output += ` (keyword: ${diag.keyword})`;
858
- }
859
- if (diag.source) {
860
- output += ` [${diag.source}]`;
861
- }
862
- });
863
- }
864
- if (this.source) {
865
- output += `
866
-
867
- Source: ${this.source.type}`;
868
- if (this.source.id) {
869
- output += ` (${this.source.id})`;
870
- }
871
- }
872
- return output;
873
- }
874
- /**
875
- * Convert to JSON representation
876
- */
877
- toJSON() {
878
- return {
879
- name: this.name,
880
- message: this.message,
881
- schemaId: this.schemaId,
882
- diagnostics: this.diagnostics,
883
- source: this.source,
884
- cause: this.cause?.message
885
- };
886
- }
887
- };
888
- }
889
- });
890
-
891
- // src/schema/utils.ts
892
- function createDiagnostic(pointer, message, keyword, severity = "ERROR", source = "ajv", data) {
893
- return {
894
- pointer,
895
- message,
896
- keyword,
897
- severity,
898
- source,
899
- data
900
- };
901
- }
902
- var init_utils = __esm({
903
- "src/schema/utils.ts"() {
904
- init_errors();
905
- }
906
- });
907
- var init_goneat_bridge = __esm({
908
- "src/schema/goneat-bridge.ts"() {
909
- init_utils();
910
- }
911
- });
912
- var init_normalizer = __esm({
913
- "src/schema/normalizer.ts"() {
914
- init_errors();
915
- }
916
- });
917
- function optionsChanged(newOptions) {
918
- if (!globalRegistryOptions) return false;
919
- return true;
920
- }
921
- function getSchemaRegistry(options) {
922
- if (!globalRegistry || optionsChanged()) {
923
- globalRegistry = new SchemaRegistry(options);
924
- globalRegistryOptions = options;
925
- }
926
- return globalRegistry;
927
- }
928
- var DEFAULT_PATTERNS, SchemaRegistry, globalRegistry, globalRegistryOptions;
929
- var init_registry2 = __esm({
930
- "src/schema/registry.ts"() {
931
- init_errors();
932
- DEFAULT_PATTERNS = ["**/*.schema.json", "**/*.schema.yaml", "**/*.schema.yml"];
933
- SchemaRegistry = class {
934
- schemas = /* @__PURE__ */ new Map();
935
- options;
936
- constructor(options = {}) {
937
- this.options = {
938
- baseDir: options.baseDir || this.getDefaultSchemaDir(),
939
- patterns: options.patterns || DEFAULT_PATTERNS,
940
- followSymlinks: options.followSymlinks ?? false,
941
- maxDepth: options.maxDepth ?? 10
942
- };
943
- }
944
- /**
945
- * Get default schema directory using import.meta.url
946
- */
947
- getDefaultSchemaDir() {
948
- const __filename2 = fileURLToPath(import.meta.url);
949
- const __dirname3 = dirname(__filename2);
950
- return join(__dirname3, "..", "..", "schemas", "crucible-ts");
951
- }
952
- /**
953
- * Build logical schema ID from file path
954
- */
955
- buildSchemaId(filePath, baseDir) {
956
- const relativePath = relative(baseDir, filePath);
957
- const withoutExt = relativePath.replace(/\.(schema\.(json|yaml|yml))$/, "");
958
- return withoutExt.replace(/\\/g, "/");
959
- }
960
- /**
961
- * Extract schema format from file extension
962
- */
963
- getSchemaFormat(filePath) {
964
- const ext = extname(filePath).toLowerCase();
965
- switch (ext) {
966
- case ".json":
967
- return "json";
968
- case ".yaml":
969
- case ".yml":
970
- return "yaml";
971
- default:
972
- return "json";
973
- }
974
- }
975
- /**
976
- * Extract metadata from schema file
977
- */
978
- async extractMetadata(filePath) {
979
- try {
980
- const content = await readFile(filePath, "utf-8");
981
- const format = this.getSchemaFormat(filePath);
982
- let parsed;
983
- if (format === "yaml") {
984
- parsed = parse(content);
985
- } else {
986
- parsed = JSON.parse(content);
987
- }
988
- const baseDir = this.options.baseDir ?? "";
989
- const relativePath = relative(baseDir, filePath);
990
- return {
991
- id: this.buildSchemaId(filePath, baseDir),
992
- path: filePath,
993
- relativePath,
994
- format,
995
- version: parsed.$schema || parsed.version,
996
- description: parsed.title || parsed.description,
997
- schemaDraft: parsed.$schema
998
- };
999
- } catch (error) {
1000
- throw SchemaValidationError.registryError(
1001
- "metadata extraction",
1002
- `Failed to process ${filePath}: ${error.message}`
1003
- );
1004
- }
1005
- }
1006
- /**
1007
- * Discover and index all available schemas
1008
- */
1009
- async discoverSchemas() {
1010
- try {
1011
- const baseDir = this.options.baseDir ?? "";
1012
- const patterns = this.options.patterns ?? [];
1013
- if (patterns.length === 0) {
1014
- this.schemas.clear();
1015
- return;
1016
- }
1017
- const pattern = patterns.map((p) => join(baseDir, p));
1018
- try {
1019
- await access(baseDir);
1020
- } catch {
1021
- this.schemas.clear();
1022
- return;
1023
- }
1024
- const files = await glob(pattern, {
1025
- absolute: true,
1026
- followSymbolicLinks: this.options.followSymlinks,
1027
- deep: this.options.maxDepth,
1028
- onlyFiles: true,
1029
- suppressErrors: true
1030
- // Don't throw on permission errors
1031
- });
1032
- this.schemas.clear();
1033
- for (const filePath of files) {
1034
- try {
1035
- const metadata = await this.extractMetadata(filePath);
1036
- this.schemas.set(metadata.id, metadata);
1037
- } catch (error) {
1038
- console.warn(`Warning: Failed to process schema ${filePath}:`, error);
1039
- }
1040
- }
1041
- } catch (error) {
1042
- throw SchemaValidationError.registryError("discovery", error.message);
1043
- }
1044
- }
1045
- /**
1046
- * List available schemas with optional prefix filtering
1047
- */
1048
- async listSchemas(prefix) {
1049
- if (this.schemas.size === 0) {
1050
- await this.discoverSchemas();
1051
- }
1052
- const schemas = Array.from(this.schemas.values());
1053
- if (prefix) {
1054
- return schemas.filter((schema) => schema.id.startsWith(prefix));
1055
- }
1056
- return schemas;
1057
- }
1058
- /**
1059
- * Get schema by logical ID
1060
- */
1061
- async getSchema(id) {
1062
- if (this.schemas.size === 0) {
1063
- await this.discoverSchemas();
1064
- }
1065
- const schema = this.schemas.get(id);
1066
- if (!schema) {
1067
- throw SchemaValidationError.schemaNotFound(id);
1068
- }
1069
- return schema;
1070
- }
1071
- /**
1072
- * Get schema by file path
1073
- */
1074
- async getSchemaByPath(filePath) {
1075
- if (this.schemas.size === 0) {
1076
- await this.discoverSchemas();
1077
- }
1078
- const absolutePath = filePath.startsWith("/") ? filePath : join(process.cwd(), filePath);
1079
- for (const schema of this.schemas.values()) {
1080
- if (schema.path === absolutePath) {
1081
- return schema;
1082
- }
1083
- }
1084
- throw SchemaValidationError.schemaNotFound(filePath);
1085
- }
1086
- /**
1087
- * Check if schema exists
1088
- */
1089
- async hasSchema(id) {
1090
- if (this.schemas.size === 0) {
1091
- await this.discoverSchemas();
1092
- }
1093
- return this.schemas.has(id);
1094
- }
1095
- /**
1096
- * Get registry size
1097
- */
1098
- get size() {
1099
- return this.schemas.size;
1100
- }
1101
- /**
1102
- * Clear registry cache
1103
- */
1104
- clear() {
1105
- this.schemas.clear();
1106
- }
1107
- };
1108
- }
1109
- });
1110
- var init_cli = __esm({
1111
- "src/schema/cli.ts"() {
1112
- init_goneat_bridge();
1113
- init_normalizer();
1114
- init_registry2();
1115
- init_utils();
1116
- init_validator();
1117
- }
1118
- });
1119
- var init_export = __esm({
1120
- "src/schema/export.ts"() {
1121
- init_errors();
1122
- init_registry2();
1123
- init_validator();
1124
- }
1125
- });
1126
-
1127
- // src/schema/index.ts
1128
- var init_schema = __esm({
1129
- "src/schema/index.ts"() {
1130
- init_ajv_formats();
1131
- init_cli();
1132
- init_errors();
1133
- init_export();
1134
- init_goneat_bridge();
1135
- init_normalizer();
1136
- init_registry2();
1137
- init_utils();
1138
- init_validator();
1139
- }
1140
- });
1141
-
1142
- // src/telemetry/validators.ts
1143
- var init_validators = __esm({
1144
- "src/telemetry/validators.ts"() {
1145
- init_schema();
1146
- }
1147
- });
1148
-
1149
- // src/telemetry/index.ts
1150
- var metrics;
1151
- var init_telemetry = __esm({
1152
- "src/telemetry/index.ts"() {
1153
- init_registry();
1154
- init_registry();
1155
- init_counter();
1156
- init_gauge();
1157
- init_histogram();
1158
- init_taxonomy();
1159
- init_types();
1160
- init_validators();
1161
- metrics = new MetricsRegistry();
1162
- }
1163
- });
1164
- async function loadMetaSchema(draft) {
1165
- const __filename2 = fileURLToPath(import.meta.url);
1166
- const __dirname3 = dirname(__filename2);
1167
- const metaSchemaPath = join(
1168
- __dirname3,
1169
- "..",
1170
- "..",
1171
- "schemas",
1172
- "crucible-ts",
1173
- "meta",
1174
- draft,
1175
- "schema.json"
1176
- );
1177
- const content = await readFile(metaSchemaPath, "utf-8");
1178
- return JSON.parse(content);
1179
- }
1180
- async function loadVocabularySchemas(draft) {
1181
- if (draft !== "draft-2019-09" && draft !== "draft-2020-12") {
1182
- return [];
1183
- }
1184
- const __filename2 = fileURLToPath(import.meta.url);
1185
- const __dirname3 = dirname(__filename2);
1186
- const vocabDir = join(__dirname3, "..", "..", "schemas", "crucible-ts", "meta", draft, "meta");
1187
- const vocabFiles = draft === "draft-2020-12" ? [
1188
- "core.json",
1189
- "applicator.json",
1190
- "unevaluated.json",
1191
- "validation.json",
1192
- "meta-data.json",
1193
- "format-annotation.json",
1194
- "content.json"
1195
- ] : [
1196
- "core.json",
1197
- "applicator.json",
1198
- "validation.json",
1199
- "meta-data.json",
1200
- "format.json",
1201
- "content.json"
1202
- ];
1203
- const schemas = [];
1204
- for (const file of vocabFiles) {
1205
- try {
1206
- const content = await readFile(join(vocabDir, file), "utf-8");
1207
- schemas.push(JSON.parse(content));
1208
- } catch {
1209
- }
1210
- }
1211
- return schemas;
1212
- }
1213
- async function loadReferencedSchema(uri) {
1214
- const __filename2 = fileURLToPath(import.meta.url);
1215
- const __dirname3 = dirname(__filename2);
1216
- const repoRoot = join(__dirname3, "..", "..");
1217
- let resolvedPath;
1218
- if (uri.startsWith("https://schemas.fulmenhq.dev/")) {
1219
- let relativePath = uri.replace("https://schemas.fulmenhq.dev/", "");
1220
- if (relativePath.startsWith("crucible/")) {
1221
- relativePath = relativePath.slice("crucible/".length);
1222
- }
1223
- if (relativePath.startsWith("config/taxonomy/")) {
1224
- resolvedPath = join(
1225
- repoRoot,
1226
- "config",
1227
- "crucible-ts",
1228
- "taxonomy",
1229
- relativePath.split("/").pop() || ""
1230
- );
1231
- } else {
1232
- resolvedPath = join(repoRoot, "schemas", "crucible-ts", relativePath);
1233
- }
1234
- } else if (uri.startsWith("../../") || uri.startsWith("../")) {
1235
- const schemaBase = join(
1236
- repoRoot,
1237
- "schemas",
1238
- "crucible-ts",
1239
- "observability",
1240
- "metrics",
1241
- "v1.0.0"
1242
- );
1243
- resolvedPath = join(schemaBase, uri);
1244
- } else if (uri.startsWith("file://")) {
1245
- resolvedPath = fileURLToPath(uri);
1246
- } else {
1247
- throw new Error(`Cannot load remote schema: ${uri}`);
1248
- }
1249
- const content = await readFile(resolvedPath, "utf-8");
1250
- const ext = resolvedPath.split(".").pop()?.toLowerCase();
1251
- if (ext === "yaml" || ext === "yml") {
1252
- return parse(content);
1253
- }
1254
- return JSON.parse(content);
1255
- }
1256
- function detectDialect(schema) {
1257
- if (schema && typeof schema === "object" && !Array.isArray(schema)) {
1258
- const maybeSchema = schema;
1259
- const declared = maybeSchema.$schema;
1260
- if (typeof declared === "string") {
1261
- if (declared.includes("draft-04")) return "draft-04";
1262
- if (declared.includes("draft-06")) return "draft-06";
1263
- if (declared.includes("draft-07")) return "draft-07";
1264
- if (declared.includes("draft/2019-09")) return "draft-2019-09";
1265
- if (declared.includes("draft/2020-12")) return "draft-2020-12";
1266
- }
1267
- }
1268
- return "draft-2020-12";
1269
- }
1270
- function createAjv(dialect) {
1271
- const AjvCtor = dialect === "draft-2020-12" ? Ajv2020 : dialect === "draft-2019-09" ? Ajv2019 : dialect === "draft-04" ? AjvDraft04 : Ajv;
1272
- const ajv = new AjvCtor({
1273
- strict: false,
1274
- allErrors: true,
1275
- verbose: true,
1276
- // Allow schemas with $id to be added without replacing existing ones
1277
- addUsedSchema: false,
1278
- // draft-04 uses "id"; later drafts use "$id"
1279
- schemaId: dialect === "draft-04" ? "id" : "$id",
1280
- // Enable async schema loading for YAML references
1281
- loadSchema: loadReferencedSchema
1282
- });
1283
- applyFulmenAjvFormats(ajv);
1284
- return ajv;
1285
- }
1286
- async function getAjv(dialect) {
1287
- const existing = ajvInstances.get(dialect);
1288
- if (existing) {
1289
- const ready = metaschemaReady.get(dialect);
1290
- if (ready) await ready;
1291
- return existing;
1292
- }
1293
- const ajv = createAjv(dialect);
1294
- ajvInstances.set(dialect, ajv);
1295
- const readyPromise = Promise.all([loadVocabularySchemas(dialect), loadMetaSchema(dialect)]).then(([vocabSchemas, metaSchema]) => {
1296
- for (const vocabSchema of vocabSchemas) {
1297
- try {
1298
- ajv.addMetaSchema(vocabSchema);
1299
- } catch {
1300
- }
1301
- }
1302
- try {
1303
- ajv.addMetaSchema(metaSchema);
1304
- } catch {
1305
- }
1306
- }).catch((error) => {
1307
- throw new Error(`Failed to load metaschemas (${dialect}): ${error}`);
1308
- });
1309
- metaschemaReady.set(dialect, readyPromise);
1310
- await readyPromise;
1311
- return ajv;
1312
- }
1313
- async function compileSchema(schema, options = {}) {
1314
- const baseKey = typeof schema === "string" ? schema : JSON.stringify(schema);
1315
- let parsedSchema;
1316
- if (typeof schema === "string") {
1317
- try {
1318
- parsedSchema = JSON.parse(schema);
1319
- } catch {
1320
- parsedSchema = parse(schema);
1321
- }
1322
- } else if (Buffer.isBuffer(schema)) {
1323
- const content = schema.toString("utf-8");
1324
- try {
1325
- parsedSchema = JSON.parse(content);
1326
- } catch {
1327
- parsedSchema = parse(content);
1328
- }
1329
- } else {
1330
- parsedSchema = schema;
1331
- }
1332
- const dialect = detectDialect(parsedSchema);
1333
- const ajv = await getAjv(dialect);
1334
- const cacheKey = `${dialect}:${baseKey}`;
1335
- const cached = schemaCache.get(cacheKey);
1336
- if (cached !== void 0) {
1337
- return cached;
1338
- }
1339
- try {
1340
- if (options.aliases && options.aliases.length > 0) {
1341
- for (const alias of options.aliases) {
1342
- if (alias && ajv.getSchema(alias) === void 0) {
1343
- try {
1344
- if (typeof parsedSchema === "object" && parsedSchema !== null) {
1345
- ajv.addSchema(parsedSchema, alias);
1346
- }
1347
- } catch {
1348
- }
1349
- }
1350
- }
1351
- }
1352
- const validator = typeof parsedSchema === "boolean" ? ajv.compile(parsedSchema) : await ajv.compileAsync(parsedSchema);
1353
- schemaCache.set(cacheKey, validator);
1354
- return validator;
1355
- } catch (error) {
1356
- throw SchemaValidationError.parseFailed(
1357
- {
1358
- type: "string",
1359
- content: typeof schema === "string" ? schema : JSON.stringify(schema)
1360
- },
1361
- error
1362
- );
1363
- }
1364
- }
1365
- function validateData(data, validator) {
1366
- const valid = validator(data);
1367
- const result = {
1368
- valid,
1369
- diagnostics: [],
1370
- source: "ajv"
1371
- };
1372
- if (!valid && validator.errors) {
1373
- const errors = validator.errors;
1374
- if (Array.isArray(errors)) {
1375
- result.diagnostics = errors.map(
1376
- (error) => createDiagnostic(
1377
- error.instancePath || "",
1378
- error.message || "Validation failed",
1379
- error.keyword || "unknown",
1380
- "ERROR",
1381
- "ajv"
1382
- )
1383
- );
1384
- }
1385
- metrics.counter("schema_validation_errors").inc();
1386
- } else {
1387
- metrics.counter("schema_validations").inc();
1388
- }
1389
- return result;
1390
- }
1391
- async function compileSchemaById(schemaId, registryOptions) {
1392
- try {
1393
- const registry = getSchemaRegistry(registryOptions);
1394
- const metadata = await registry.getSchema(schemaId);
1395
- const content = await readFile(metadata.path, "utf-8");
1396
- const aliases = [];
1397
- const normalizedRelativePath = metadata.relativePath.replace(/\\/g, "/");
1398
- if (normalizedRelativePath) {
1399
- aliases.push(
1400
- new URL(`crucible/${normalizedRelativePath}`, "https://schemas.fulmenhq.dev/").toString()
1401
- );
1402
- }
1403
- return compileSchema(content, { aliases });
1404
- } catch (error) {
1405
- metrics.counter("schema_validation_errors").inc();
1406
- throw error;
1407
- }
1408
- }
1409
- async function validateDataBySchemaId(data, schemaId, registryOptions) {
1410
- try {
1411
- const validator = await compileSchemaById(schemaId, registryOptions);
1412
- return validateData(data, validator);
1413
- } catch (error) {
1414
- metrics.counter("schema_validation_errors").inc();
1415
- throw error;
1416
- }
1417
- }
1418
- var ajvInstances, metaschemaReady, schemaCache;
1419
- var init_validator = __esm({
1420
- "src/schema/validator.ts"() {
1421
- init_telemetry();
1422
- init_ajv_formats();
1423
- init_errors();
1424
- init_registry2();
1425
- init_utils();
1426
- ajvInstances = /* @__PURE__ */ new Map();
1427
- metaschemaReady = /* @__PURE__ */ new Map();
1428
- schemaCache = /* @__PURE__ */ new Map();
1429
- }
1430
- });
1431
-
1432
- // src/foundry/errors.ts
1433
- var FoundryCatalogError;
1434
- var init_errors2 = __esm({
1435
- "src/foundry/errors.ts"() {
1436
- FoundryCatalogError = class _FoundryCatalogError extends Error {
1437
- constructor(message, catalog, cause) {
1438
- super(message);
1439
- this.catalog = catalog;
1440
- this.cause = cause;
1441
- this.name = "FoundryCatalogError";
1442
- if (Error.captureStackTrace) {
1443
- Error.captureStackTrace(this, _FoundryCatalogError);
1444
- }
1445
- }
1446
- static invalidSchema(catalog, details, cause) {
1447
- return new _FoundryCatalogError(
1448
- `Invalid schema in ${catalog} catalog: ${details}`,
1449
- catalog,
1450
- cause
1451
- );
1452
- }
1453
- static missingCatalog(catalog) {
1454
- return new _FoundryCatalogError(`Catalog ${catalog} not found or could not be loaded`, catalog);
1455
- }
1456
- static invalidPattern(patternId, details) {
1457
- return new _FoundryCatalogError(`Invalid pattern ${patternId}: ${details}`, "patterns");
1458
- }
1459
- static compilationError(patternId, details, cause) {
1460
- return new _FoundryCatalogError(
1461
- `Failed to compile pattern ${patternId}: ${details}`,
1462
- "patterns",
1463
- cause
1464
- );
1465
- }
1466
- };
1467
- }
1468
- });
1469
- function getConfigPath() {
1470
- if (__dirname2.includes("/dist/")) {
1471
- return join(__dirname2, "../../config/crucible-ts/library/foundry/signals.yaml");
1472
- }
1473
- return join(__dirname2, "../../../config/crucible-ts/library/foundry/signals.yaml");
1474
- }
1475
- async function loadCatalog() {
1476
- const filePath = SSOT_PATHS.signals;
1477
- const catalogName = "signals";
1478
- try {
1479
- let content;
1480
- if (typeof Bun !== "undefined") {
1481
- try {
1482
- const file = Bun.file(filePath);
1483
- if (!await file.exists()) {
1484
- throw FoundryCatalogError.missingCatalog(catalogName);
1485
- }
1486
- content = await file.text();
1487
- } catch (error) {
1488
- if (error instanceof Error && error.message.includes("No such file")) {
1489
- throw FoundryCatalogError.missingCatalog(catalogName);
1490
- }
1491
- throw error;
1492
- }
1493
- } else {
1494
- try {
1495
- content = await readFile(filePath, "utf-8");
1496
- } catch (error) {
1497
- if (error.code === "ENOENT") {
1498
- throw FoundryCatalogError.missingCatalog(catalogName);
1499
- }
1500
- throw error;
1501
- }
1502
- }
1503
- const data = parse(content);
1504
- const result = await validateDataBySchemaId(data, SCHEMA_ID);
1505
- if (!result.valid) {
1506
- const errorMessages = result.diagnostics.map((d) => `${d.pointer}: ${d.message}`).join("; ");
1507
- throw FoundryCatalogError.invalidSchema(
1508
- catalogName,
1509
- `Schema validation failed: ${errorMessages}`
1510
- );
1511
- }
1512
- return data;
1513
- } catch (error) {
1514
- if (error instanceof FoundryCatalogError) {
1515
- throw error;
1516
- }
1517
- const err = error;
1518
- if (err.code === "ENOENT") {
1519
- throw FoundryCatalogError.missingCatalog(catalogName);
1520
- } else if (err.code === "EACCES") {
1521
- throw FoundryCatalogError.invalidSchema(
1522
- catalogName,
1523
- "Permission denied accessing catalog file",
1524
- err
1525
- );
1526
- } else if (err.code === "EISDIR") {
1527
- throw FoundryCatalogError.invalidSchema(
1528
- catalogName,
1529
- "Expected file but found directory",
1530
- err
1531
- );
1532
- } else if (err.code === "EMFILE" || err.code === "ENFILE") {
1533
- throw FoundryCatalogError.invalidSchema(catalogName, "Too many open files", err);
1534
- }
1535
- throw FoundryCatalogError.invalidSchema(catalogName, "Failed to load catalog", err);
1536
- }
1537
- }
1538
- async function getCatalog() {
1539
- if (!cachedCatalog) {
1540
- cachedCatalog = await loadCatalog();
1541
- }
1542
- return cachedCatalog;
1543
- }
1544
- async function getSignalsVersion() {
1545
- const catalog = await getCatalog();
1546
- return catalog.version;
1547
- }
1548
- async function listSignals() {
1549
- const catalog = await getCatalog();
1550
- return catalog.signals.map((signal) => ({ ...signal }));
1551
- }
1552
- async function getSignal(identifier) {
1553
- const catalog = await getCatalog();
1554
- const signal = catalog.signals.find((s) => s.id === identifier || s.name === identifier);
1555
- return signal ? { ...signal } : null;
1556
- }
1557
- async function listBehaviors() {
1558
- const catalog = await getCatalog();
1559
- return catalog.behaviors.map((behavior) => ({ ...behavior }));
1560
- }
1561
- async function getBehavior(id) {
1562
- const catalog = await getCatalog();
1563
- const behavior = catalog.behaviors.find((b) => b.id === id);
1564
- return behavior ? { ...behavior } : null;
1565
- }
1566
- async function getSignalCatalog() {
1567
- return await getCatalog();
1568
- }
1569
- var __filename, __dirname2, SSOT_PATHS, SCHEMA_ID, cachedCatalog;
1570
- var init_catalog = __esm({
1571
- "src/foundry/signals/catalog.ts"() {
1572
- init_validator();
1573
- init_errors2();
1574
- __filename = fileURLToPath(import.meta.url);
1575
- __dirname2 = dirname(__filename);
1576
- SSOT_PATHS = {
1577
- signals: getConfigPath()
1578
- };
1579
- SCHEMA_ID = "library/foundry/v1.0.0/signals";
1580
- cachedCatalog = null;
1581
- }
1582
- });
1583
-
1584
- // src/foundry/signals/capabilities.ts
1585
- function getPlatform() {
1586
- const platform = process.platform;
1587
- switch (platform) {
1588
- case "linux":
1589
- return "linux";
1590
- case "darwin":
1591
- return "darwin";
1592
- case "win32":
1593
- return "win32";
1594
- case "freebsd":
1595
- return "freebsd";
1596
- default:
1597
- return "unknown";
1598
- }
1599
- }
1600
- function isPOSIX() {
1601
- const platform = getPlatform();
1602
- return platform === "linux" || platform === "darwin" || platform === "freebsd";
1603
- }
1604
- function isWindows() {
1605
- return getPlatform() === "win32";
1606
- }
1607
- async function supportsSignal(signalName) {
1608
- const signal = await getSignal(signalName);
1609
- if (!signal) {
1610
- return false;
1611
- }
1612
- if (isPOSIX()) {
1613
- return true;
1614
- }
1615
- if (isWindows()) {
1616
- return signal.windows_event !== null;
1617
- }
1618
- return false;
1619
- }
1620
- function supportsSignalExitCodes() {
1621
- return isPOSIX();
1622
- }
1623
- async function getPlatformCapabilities() {
1624
- const platform = getPlatform();
1625
- const isPosix = isPOSIX();
1626
- const isWin = isWindows();
1627
- const catalog = await getSignalCatalog();
1628
- const supported = [];
1629
- const unsupported = [];
1630
- const mapped = [];
1631
- for (const signal of catalog.signals) {
1632
- if (isPosix) {
1633
- supported.push(signal.name);
1634
- } else if (isWin) {
1635
- if (signal.windows_event !== null) {
1636
- supported.push(signal.name);
1637
- mapped.push(signal.name);
1638
- } else {
1639
- unsupported.push(signal.name);
1640
- }
1641
- } else {
1642
- unsupported.push(signal.name);
1643
- }
1644
- }
1645
- return {
1646
- platform,
1647
- isPOSIX: isPosix,
1648
- isWindows: isWin,
1649
- supportsNativeSignals: isPosix,
1650
- supportsSignalExitCodes: supportsSignalExitCodes(),
1651
- supportedSignals: supported,
1652
- unsupportedSignals: unsupported,
1653
- mappedSignals: mapped
1654
- };
1655
- }
1656
- async function getSignalNumber(signalName) {
1657
- const signal = await getSignal(signalName);
1658
- if (!signal) {
1659
- return null;
1660
- }
1661
- const platform = getPlatform();
1662
- if (signal.platform_overrides) {
1663
- if (platform === "darwin" && signal.platform_overrides.darwin !== void 0) {
1664
- return signal.platform_overrides.darwin;
1665
- }
1666
- if (platform === "freebsd" && signal.platform_overrides.freebsd !== void 0) {
1667
- return signal.platform_overrides.freebsd;
1668
- }
1669
- }
1670
- return signal.unix_number;
1671
- }
1672
- async function getWindowsEvent(signalName) {
1673
- const signal = await getSignal(signalName);
1674
- if (!signal) {
1675
- return null;
1676
- }
1677
- return signal.windows_event;
1678
- }
1679
- var init_capabilities = __esm({
1680
- "src/foundry/signals/capabilities.ts"() {
1681
- init_catalog();
1682
- }
1683
- });
1684
-
1685
- // src/foundry/signals/config-reload-endpoint.ts
1686
- function createConfigReloadEndpoint(options) {
1687
- const { loader, validator, onReload: onReload2, auth, rateLimit, logger, telemetry } = options;
1688
- return async (payload, req) => {
1689
- const correlationId = payload.correlation_id ?? generateCorrelationId();
1690
- const authResult = await auth(req);
1691
- if (!authResult.authenticated) {
1692
- if (logger) {
1693
- logger.warn("Config reload endpoint: authentication failed", {
1694
- correlation_id: correlationId,
1695
- reason: authResult.reason
1696
- });
1697
- }
1698
- if (telemetry) {
1699
- telemetry.emit("fulmen.config.http_endpoint.auth_failed", {
1700
- correlation_id: correlationId
1701
- });
1702
- }
1703
- return {
1704
- status: "error",
1705
- error: "authentication_failed",
1706
- message: authResult.reason || "Authentication required",
1707
- statusCode: 401
1708
- };
1709
- }
1710
- const identity = authResult.identity || "unknown";
1711
- if (rateLimit) {
1712
- const rateLimitResult = await rateLimit(identity);
1713
- if (!rateLimitResult.allowed) {
1714
- if (logger) {
1715
- logger.warn("Config reload endpoint: rate limit exceeded", {
1716
- correlation_id: correlationId,
1717
- identity
1718
- });
1719
- }
1720
- if (telemetry) {
1721
- telemetry.emit("fulmen.config.http_endpoint.rate_limited", {
1722
- correlation_id: correlationId
1723
- });
1724
- }
1725
- return {
1726
- status: "error",
1727
- error: "rate_limit_exceeded",
1728
- message: "Rate limit exceeded. Please try again later.",
1729
- statusCode: 429
1730
- };
1731
- }
1732
- }
1733
- if (telemetry) {
1734
- telemetry.emit("fulmen.config.http_endpoint.reload_requested", {
1735
- correlation_id: correlationId
1736
- });
1737
- }
1738
- try {
1739
- const config = await loader();
1740
- if (validator) {
1741
- const validation = await validator(config);
1742
- if (!validation.valid) {
1743
- if (logger) {
1744
- logger.warn("Config reload endpoint: validation failed", {
1745
- correlation_id: correlationId,
1746
- error_count: validation.errors?.length ?? 0
1747
- });
1748
- }
1749
- if (telemetry) {
1750
- telemetry.emit("fulmen.config.http_endpoint.reload_rejected", {
1751
- correlation_id: correlationId,
1752
- reason: "validation_failed"
1753
- });
1754
- }
1755
- return {
1756
- status: "error",
1757
- error: "validation_failed",
1758
- message: "Configuration validation failed",
1759
- validation_errors: validation.errors,
1760
- statusCode: 422
1761
- };
1762
- }
1763
- }
1764
- if (onReload2) {
1765
- await onReload2(config);
1766
- }
1767
- if (telemetry) {
1768
- telemetry.emit("fulmen.config.http_endpoint.reload_accepted", {
1769
- correlation_id: correlationId
1770
- });
1771
- }
1772
- if (logger) {
1773
- logger.info("Config reload endpoint: reload accepted", {
1774
- correlation_id: correlationId,
1775
- reason: payload.reason
1776
- });
1777
- }
1778
- return {
1779
- status: "reloaded",
1780
- correlation_id: correlationId,
1781
- message: "Configuration reloaded",
1782
- statusCode: 200
1783
- };
1784
- } catch (error) {
1785
- if (logger) {
1786
- logger.warn("Config reload endpoint: reload failed", {
1787
- correlation_id: correlationId,
1788
- error: error instanceof Error ? error.message : String(error)
1789
- });
1790
- }
1791
- if (telemetry) {
1792
- telemetry.emit("fulmen.config.http_endpoint.reload_error", {
1793
- correlation_id: correlationId,
1794
- error_type: error instanceof Error ? error.constructor.name : "unknown"
1795
- });
1796
- }
1797
- return {
1798
- status: "error",
1799
- error: "reload_failed",
1800
- message: error instanceof Error ? error.message : String(error),
1801
- statusCode: 500
1802
- };
1803
- }
1804
- };
1805
- }
1806
- function generateCorrelationId() {
1807
- return `cfg-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
1808
- }
1809
- var init_config_reload_endpoint = __esm({
1810
- "src/foundry/signals/config-reload-endpoint.ts"() {
1811
- }
1812
- });
1813
-
1814
- // src/appidentity/runtime.ts
1815
- function detectRuntime() {
1816
- const versions = process.versions;
1817
- if (typeof versions.bun === "string" && versions.bun.length > 0) {
1818
- return { name: "bun", version: versions.bun };
1819
- }
1820
- if (typeof versions.node === "string" && versions.node.length > 0) {
1821
- return { name: "node", version: versions.node };
1822
- }
1823
- return { name: "unknown" };
1824
- }
1825
- function buildRuntimeInfo(options = {}) {
1826
- const runtime = detectRuntime();
1827
- const serviceName = options.serviceName ?? options.identity?.app.binary_name ?? "unknown-service";
1828
- const vendor = options.vendor ?? options.identity?.app.vendor;
1829
- return {
1830
- service: {
1831
- name: serviceName,
1832
- vendor,
1833
- version: options.version
1834
- },
1835
- runtime,
1836
- platform: {
1837
- os: process.platform,
1838
- arch: process.arch
1839
- }
1840
- };
1841
- }
1842
- var init_runtime = __esm({
1843
- "src/appidentity/runtime.ts"() {
1844
- }
1845
- });
1846
-
1847
- // src/foundry/signals/control-discovery-endpoint.ts
1848
- function createControlDiscoveryEndpoint(options) {
1849
- const { identity, version, endpoints, auth, authSummary, logger, telemetry } = options;
1850
- return async (req) => {
1851
- if (auth) {
1852
- const authResult = await auth(req);
1853
- if (!authResult.authenticated) {
1854
- if (logger) {
1855
- logger.warn("Control discovery endpoint: authentication failed", {
1856
- reason: authResult.reason
1857
- });
1858
- }
1859
- if (telemetry) {
1860
- telemetry.emit("fulmen.control.discovery.auth_failed", {
1861
- service: identity.app.binary_name
1862
- });
1863
- }
1864
- return {
1865
- status: "error",
1866
- error: "authentication_failed",
1867
- message: authResult.reason || "Authentication required",
1868
- statusCode: 401
1869
- };
1870
- }
1871
- }
1872
- if (telemetry) {
1873
- telemetry.emit("fulmen.control.discovery.served", {
1874
- service: identity.app.binary_name
1875
- });
1876
- }
1877
- const runtime = buildRuntimeInfo({ identity, version });
1878
- return {
1879
- status: "ok",
1880
- service: {
1881
- name: identity.app.binary_name,
1882
- vendor: identity.app.vendor,
1883
- version
1884
- },
1885
- runtime: {
1886
- name: runtime.runtime.name,
1887
- version: runtime.runtime.version,
1888
- platform: runtime.platform.os,
1889
- arch: runtime.platform.arch
1890
- },
1891
- auth_summary: authSummary,
1892
- endpoints,
1893
- statusCode: 200
1894
- };
1895
- };
1896
- }
1897
- var init_control_discovery_endpoint = __esm({
1898
- "src/foundry/signals/control-discovery-endpoint.ts"() {
1899
- init_runtime();
1900
- }
1901
- });
1902
-
1903
- // src/foundry/signals/convenience.ts
1904
- async function onShutdown(manager, handler, options = {}) {
1905
- await manager.register("SIGTERM", handler, options);
1906
- await manager.register("SIGINT", handler, options);
1907
- }
1908
- async function onReload(manager, handler, options = {}) {
1909
- await manager.register("SIGHUP", handler, options);
1910
- }
1911
- async function onUSR1(manager, handler, options = {}) {
1912
- await manager.register("SIGUSR1", handler, options);
1913
- }
1914
- async function onUSR2(manager, handler, options = {}) {
1915
- await manager.register("SIGUSR2", handler, options);
1916
- }
1917
- async function onEmergencyQuit(manager, handler, options = {}) {
1918
- await manager.register("SIGQUIT", handler, options);
1919
- }
1920
- async function onAnyShutdown(manager, handler, options = {}) {
1921
- await manager.register("SIGTERM", handler, options);
1922
- await manager.register("SIGINT", handler, options);
1923
- await manager.register("SIGQUIT", handler, options);
1924
- }
1925
- var init_convenience = __esm({
1926
- "src/foundry/signals/convenience.ts"() {
1927
- }
1928
- });
1929
-
1930
- // src/foundry/signals/double-tap.ts
1931
- async function createDoubleTapTracker(signalName, config = {}) {
1932
- const signal = await getSignal(signalName);
1933
- const defaultWindowMs = signal?.double_tap_window_seconds ? signal.double_tap_window_seconds * 1e3 : 2e3;
1934
- const defaultExitCode = signal?.double_tap_exit_code || 130;
1935
- const defaultHintMessage = signal?.double_tap_message || "Press Ctrl+C again within 2s to force quit";
1936
- return {
1937
- firstTapTime: null,
1938
- windowMs: config.windowMs ?? defaultWindowMs,
1939
- exitCode: config.exitCode ?? defaultExitCode,
1940
- hintMessage: config.hintMessage ?? defaultHintMessage,
1941
- logger: config.logger,
1942
- testMode: config.testMode ?? false
1943
- };
1944
- }
1945
- function handleDoubleTap(state) {
1946
- const now = Date.now();
1947
- if (state.firstTapTime === null) {
1948
- state.firstTapTime = now;
1949
- if (state.logger) {
1950
- state.logger.info(state.hintMessage);
1951
- } else {
1952
- console.log(state.hintMessage);
1953
- }
1954
- return false;
1955
- }
1956
- const elapsed = now - state.firstTapTime;
1957
- if (elapsed < state.windowMs) {
1958
- if (state.logger) {
1959
- state.logger.info("Force quitting...");
1960
- } else {
1961
- console.log("Force quitting...");
1962
- }
1963
- if (!state.testMode) {
1964
- process.exit(state.exitCode);
1965
- }
1966
- return true;
1967
- }
1968
- state.firstTapTime = now;
1969
- if (state.logger) {
1970
- state.logger.info(state.hintMessage);
1971
- } else {
1972
- console.log(state.hintMessage);
1973
- }
1974
- return false;
1975
- }
1976
- function resetDoubleTap(state) {
1977
- state.firstTapTime = null;
1978
- }
1979
- function isWithinWindow(state) {
1980
- if (state.firstTapTime === null) {
1981
- return false;
1982
- }
1983
- const elapsed = Date.now() - state.firstTapTime;
1984
- return elapsed < state.windowMs;
1985
- }
1986
- function getWindowTimeRemaining(state) {
1987
- if (state.firstTapTime === null) {
1988
- return null;
1989
- }
1990
- const elapsed = Date.now() - state.firstTapTime;
1991
- const remaining = state.windowMs - elapsed;
1992
- return remaining > 0 ? remaining : null;
1993
- }
1994
- var init_double_tap = __esm({
1995
- "src/foundry/signals/double-tap.ts"() {
1996
- init_catalog();
1997
- }
1998
- });
1999
-
2000
- // src/foundry/signals/windows.ts
2001
- async function handleWindowsFallback(signalName, options = {}) {
2002
- const signal = await getSignal(signalName);
2003
- if (!signal) {
2004
- return {
2005
- supported: false,
2006
- logged: false
2007
- };
2008
- }
2009
- if (signal.windows_event !== null) {
2010
- return {
2011
- supported: true,
2012
- logged: false
2013
- };
2014
- }
2015
- const fallback = signal.windows_fallback;
2016
- if (!fallback) {
2017
- return {
2018
- supported: false,
2019
- logged: false
2020
- };
2021
- }
2022
- if (options.silent) {
2023
- return {
2024
- supported: false,
2025
- fallback,
2026
- logged: false
2027
- };
2028
- }
2029
- const logger = options.logger || defaultLogger;
2030
- const telemetry = options.telemetry;
2031
- const logMeta = {
2032
- signal: signal.name,
2033
- platform: "windows",
2034
- fallback: fallback.fallback_behavior,
2035
- operation_hint: fallback.operation_hint
2036
- };
2037
- logger.info(fallback.log_message, logMeta);
2038
- if (telemetry) {
2039
- telemetry.emit(fallback.telemetry_event, fallback.telemetry_tags);
2040
- }
2041
- return {
2042
- supported: false,
2043
- fallback,
2044
- logged: true
2045
- };
2046
- }
2047
- async function getFallbackMetadata(signalName) {
2048
- const signal = await getSignal(signalName);
2049
- if (!signal || !signal.windows_fallback) {
2050
- return null;
2051
- }
2052
- return signal.windows_fallback;
2053
- }
2054
- async function requiresFallback(signalName) {
2055
- const signal = await getSignal(signalName);
2056
- if (!signal) {
2057
- return false;
2058
- }
2059
- return signal.windows_event === null && signal.windows_fallback !== void 0;
2060
- }
2061
- async function getHttpFallbackGuidance(signalName) {
2062
- const fallback = await getFallbackMetadata(signalName);
2063
- if (!fallback || fallback.fallback_behavior !== "http_admin_endpoint") {
2064
- return null;
2065
- }
2066
- return fallback.operation_hint;
2067
- }
2068
- var defaultLogger;
2069
- var init_windows = __esm({
2070
- "src/foundry/signals/windows.ts"() {
2071
- init_catalog();
2072
- defaultLogger = {
2073
- info(message, meta) {
2074
- if (meta) {
2075
- console.info(message, meta);
2076
- } else {
2077
- console.info(message);
2078
- }
2079
- },
2080
- warn(message, meta) {
2081
- if (meta) {
2082
- console.warn(message, meta);
2083
- } else {
2084
- console.warn(message);
2085
- }
2086
- }
2087
- };
2088
- }
2089
- });
2090
-
2091
- // src/foundry/signals/guards.ts
2092
- async function ensureSupported(signalName, options = {}) {
2093
- const { includeGuidance = true } = options;
2094
- const signal = await getSignal(signalName);
2095
- if (!signal) {
2096
- throw FoundryCatalogError.invalidSchema(
2097
- "signals",
2098
- `Signal "${signalName}" not found in catalog. Valid signals: SIGTERM, SIGINT, SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGUSR1, SIGUSR2`
2099
- );
2100
- }
2101
- const supported = await supportsSignal(signalName);
2102
- if (supported) {
2103
- return;
2104
- }
2105
- let message = `Signal ${signal.name} is not supported on this platform`;
2106
- if (isWindows() && includeGuidance) {
2107
- const fallback = await getFallbackMetadata(signalName);
2108
- if (fallback) {
2109
- message += `. ${fallback.log_message}`;
2110
- switch (fallback.fallback_behavior) {
2111
- case "http_admin_endpoint":
2112
- message += `. Use HTTP endpoint: ${fallback.operation_hint}`;
2113
- break;
2114
- case "exception_handling":
2115
- message += `. Alternative: ${fallback.operation_hint}`;
2116
- break;
2117
- case "timer_api":
2118
- message += `. Alternative: ${fallback.operation_hint}`;
2119
- break;
2120
- }
2121
- }
2122
- }
2123
- throw FoundryCatalogError.invalidSchema("signals", message);
2124
- }
2125
- function ensureSignalExitCodesSupported() {
2126
- if (!isPOSIX()) {
2127
- throw FoundryCatalogError.invalidSchema(
2128
- "signals",
2129
- "Signal-based exit codes (128+N pattern) are not supported on this platform. Windows does not propagate signal numbers via exit codes. Use explicit exit codes or monitor via HTTP admin endpoint."
2130
- );
2131
- }
2132
- }
2133
- function ensurePOSIX() {
2134
- if (!isPOSIX()) {
2135
- throw FoundryCatalogError.invalidSchema(
2136
- "signals",
2137
- "This operation requires a POSIX-compliant platform (Linux, macOS, FreeBSD). Current platform does not support native signal handling."
2138
- );
2139
- }
2140
- }
2141
- function ensureWindows() {
2142
- if (!isWindows()) {
2143
- throw FoundryCatalogError.invalidSchema(
2144
- "signals",
2145
- "This operation requires Windows platform. Current platform uses native signals."
2146
- );
2147
- }
2148
- }
2149
- var init_guards = __esm({
2150
- "src/foundry/signals/guards.ts"() {
2151
- init_errors2();
2152
- init_capabilities();
2153
- init_catalog();
2154
- init_windows();
2155
- }
2156
- });
2157
-
2158
- // src/foundry/signals/http-helper.ts
2159
- function createSignalEndpoint(options) {
2160
- const { manager, auth, rateLimit, logger, telemetry, allowedSignals } = options;
2161
- return async (payload, req) => {
2162
- const correlationId = payload.correlation_id ?? generateCorrelationId2();
2163
- const authResult = await auth(req);
2164
- if (!authResult.authenticated) {
2165
- if (logger) {
2166
- logger.warn("Signal endpoint: authentication failed", {
2167
- correlation_id: correlationId,
2168
- reason: authResult.reason
2169
- });
2170
- }
2171
- if (telemetry) {
2172
- telemetry.emit("fulmen.signal.http_endpoint.auth_failed", {
2173
- correlation_id: correlationId
2174
- });
2175
- }
2176
- return {
2177
- status: "error",
2178
- error: "authentication_failed",
2179
- message: authResult.reason || "Authentication required",
2180
- statusCode: 401
2181
- };
2182
- }
2183
- const identity = authResult.identity || "unknown";
2184
- if (!payload.signal) {
2185
- return {
2186
- status: "error",
2187
- error: "invalid_request",
2188
- message: "Signal name is required",
2189
- statusCode: 400
2190
- };
2191
- }
2192
- const signalName = normalizeSignalName(payload.signal);
2193
- const defaultAllowed = [
2194
- "SIGHUP",
2195
- "SIGTERM",
2196
- "SIGINT",
2197
- "SIGQUIT",
2198
- "SIGUSR1",
2199
- "SIGUSR2",
2200
- "SIGPIPE",
2201
- "SIGALRM"
2202
- ];
2203
- const allowed = (allowedSignals || defaultAllowed).map((s) => normalizeSignalName(s));
2204
- if (!allowed.includes(signalName)) {
2205
- return {
2206
- status: "error",
2207
- error: "invalid_signal",
2208
- message: `Signal '${payload.signal}' is not recognized. Valid signals: ${allowed.join(", ")}`,
2209
- valid_signals: allowed,
2210
- statusCode: 400
2211
- };
2212
- }
2213
- const supported = await supportsSignal(signalName);
2214
- if (!supported) {
2215
- return {
2216
- status: "error",
2217
- error: "signal_not_supported",
2218
- message: `Signal ${signalName} is not supported on this platform`,
2219
- statusCode: 400
2220
- };
2221
- }
2222
- if (rateLimit) {
2223
- const rateLimitResult = await rateLimit(identity, signalName);
2224
- if (!rateLimitResult.allowed) {
2225
- if (logger) {
2226
- logger.warn("Signal endpoint: rate limit exceeded", {
2227
- correlation_id: correlationId,
2228
- identity,
2229
- signal: signalName
2230
- });
2231
- }
2232
- if (telemetry) {
2233
- telemetry.emit("fulmen.signal.http_endpoint.rate_limited", {
2234
- correlation_id: correlationId,
2235
- signal: signalName
2236
- });
2237
- }
2238
- return {
2239
- status: "error",
2240
- error: "rate_limit_exceeded",
2241
- message: "Rate limit exceeded. Please try again later.",
2242
- statusCode: 429
2243
- };
2244
- }
2245
- }
2246
- if (logger) {
2247
- logger.info("Signal endpoint: signal received", {
2248
- correlation_id: correlationId,
2249
- identity,
2250
- signal: signalName,
2251
- reason: payload.reason
2252
- });
2253
- }
2254
- if (telemetry) {
2255
- telemetry.emit("fulmen.signal.http_endpoint.signal_received", {
2256
- correlation_id: correlationId,
2257
- signal: signalName
2258
- });
2259
- }
2260
- void manager.trigger(signalName).catch((error) => {
2261
- if (logger) {
2262
- logger.warn("Signal handler execution failed", {
2263
- correlation_id: correlationId,
2264
- signal: signalName,
2265
- error: error instanceof Error ? error.message : String(error)
2266
- });
2267
- }
2268
- });
2269
- return {
2270
- status: "accepted",
2271
- signal: signalName,
2272
- correlation_id: correlationId,
2273
- message: "Signal will be processed asynchronously",
2274
- statusCode: 202
2275
- };
2276
- };
2277
- }
2278
- function normalizeSignalName(signal) {
2279
- const upper = signal.toUpperCase();
2280
- if (upper.startsWith("SIG")) {
2281
- return upper;
2282
- }
2283
- return `SIG${upper}`;
2284
- }
2285
- function generateCorrelationId2() {
2286
- return `sig-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
2287
- }
2288
- function createBearerTokenAuth(expectedToken) {
2289
- return (req) => {
2290
- const headers = req.headers;
2291
- const authHeader = headers?.authorization || headers?.Authorization;
2292
- if (!authHeader) {
2293
- return {
2294
- authenticated: false,
2295
- reason: "Missing Authorization header"
2296
- };
2297
- }
2298
- const [scheme, token] = authHeader.split(" ");
2299
- if (scheme !== "Bearer" || token !== expectedToken) {
2300
- return {
2301
- authenticated: false,
2302
- reason: "Invalid bearer token"
2303
- };
2304
- }
2305
- return {
2306
- authenticated: true,
2307
- identity: "bearer-token-user"
2308
- };
2309
- };
2310
- }
2311
- function createSimpleRateLimiter(requestsPerMinute) {
2312
- const requests = /* @__PURE__ */ new Map();
2313
- const windowMs = 6e4;
2314
- return (identity) => {
2315
- const now = Date.now();
2316
- const key = identity;
2317
- let timestamps = requests.get(key) || [];
2318
- timestamps = timestamps.filter((ts) => now - ts < windowMs);
2319
- if (timestamps.length >= requestsPerMinute) {
2320
- const oldestTimestamp = Math.min(...timestamps);
2321
- const resetAt = oldestTimestamp + windowMs;
2322
- return {
2323
- allowed: false,
2324
- remaining: 0,
2325
- reset_at: resetAt
2326
- };
2327
- }
2328
- timestamps.push(now);
2329
- requests.set(key, timestamps);
2330
- return {
2331
- allowed: true,
2332
- remaining: requestsPerMinute - timestamps.length,
2333
- reset_at: now + windowMs
2334
- };
2335
- };
2336
- }
2337
- var init_http_helper = __esm({
2338
- "src/foundry/signals/http-helper.ts"() {
2339
- init_capabilities();
2340
- }
2341
- });
2342
-
2343
- // src/foundry/signals/manager.ts
2344
- function createSignalManager(options = {}) {
2345
- return new SignalManager(options);
2346
- }
2347
- var SignalManager;
2348
- var init_manager = __esm({
2349
- "src/foundry/signals/manager.ts"() {
2350
- init_capabilities();
2351
- init_catalog();
2352
- init_double_tap();
2353
- init_windows();
2354
- SignalManager = class {
2355
- handlers = /* @__PURE__ */ new Map();
2356
- nativeListeners = /* @__PURE__ */ new Map();
2357
- options;
2358
- handlerIdCounter = 0;
2359
- doubleTapTrackers = /* @__PURE__ */ new Map();
2360
- shuttingDown = false;
2361
- constructor(options = {}) {
2362
- this.options = {
2363
- defaultTimeoutMs: options.defaultTimeoutMs ?? 3e4,
2364
- timeoutBehavior: options.timeoutBehavior ?? "log_and_continue",
2365
- testMode: options.testMode ?? false,
2366
- doubleTapWindowMs: options.doubleTapWindowMs ?? 2e3,
2367
- doubleTapExitCode: options.doubleTapExitCode ?? 130,
2368
- logger: options.logger,
2369
- telemetry: options.telemetry
2370
- };
2371
- }
2372
- /**
2373
- * Register a signal handler
2374
- *
2375
- * @param signal - Signal name (e.g., "SIGTERM") or NodeJS.Signals
2376
- * @param handler - Handler function
2377
- * @param options - Handler options
2378
- */
2379
- async register(signal, handler, options = {}) {
2380
- const signalName = typeof signal === "string" ? signal : signal;
2381
- const supported = await supportsSignal(signalName);
2382
- if (!supported) {
2383
- if (isWindows()) {
2384
- await handleWindowsFallback(signalName, {
2385
- logger: this.options.logger,
2386
- telemetry: this.options.telemetry
2387
- });
2388
- }
2389
- return;
2390
- }
2391
- if (!this.handlers.has(signalName)) {
2392
- this.handlers.set(signalName, []);
2393
- }
2394
- const registration = {
2395
- signal: signalName,
2396
- handler,
2397
- priority: options.priority ?? 0,
2398
- timeoutMs: options.timeoutMs ?? this.options.defaultTimeoutMs,
2399
- id: options.id ?? `handler-${++this.handlerIdCounter}`,
2400
- registeredAt: Date.now()
2401
- };
2402
- const handlers = this.handlers.get(signalName);
2403
- if (!handlers) {
2404
- throw new Error(`Handler list not initialized for signal ${signalName}`);
2405
- }
2406
- handlers.push(registration);
2407
- handlers.sort((a, b) => {
2408
- if (a.priority !== b.priority) {
2409
- return b.priority - a.priority;
2410
- }
2411
- return a.registeredAt - b.registeredAt;
2412
- });
2413
- if (!this.nativeListeners.has(signalName)) {
2414
- await this.registerNativeListener(signalName);
2415
- }
2416
- if (this.options.logger) {
2417
- this.options.logger.info(`Signal handler registered: ${signalName}`, {
2418
- handler_id: registration.id,
2419
- priority: registration.priority,
2420
- timeout_ms: registration.timeoutMs
2421
- });
2422
- }
2423
- if (this.options.telemetry) {
2424
- this.options.telemetry.emit("fulmen.signal.handler_registered", {
2425
- signal: signalName,
2426
- handler_id: registration.id,
2427
- priority: String(registration.priority)
2428
- });
2429
- }
2430
- }
2431
- /**
2432
- * Register native Node.js signal listener
2433
- */
2434
- async registerNativeListener(signalName) {
2435
- const signal = await getSignal(signalName);
2436
- if (!signal) {
2437
- return;
2438
- }
2439
- const useDoubleTap = signal.default_behavior === "graceful_shutdown_with_double_tap";
2440
- if (useDoubleTap) {
2441
- const tracker = await createDoubleTapTracker(signalName, {
2442
- windowMs: this.options.doubleTapWindowMs,
2443
- exitCode: this.options.doubleTapExitCode,
2444
- logger: this.options.logger,
2445
- testMode: this.options.testMode
2446
- });
2447
- this.doubleTapTrackers.set(signalName, tracker);
2448
- }
2449
- const listener = async (sig) => {
2450
- if (useDoubleTap) {
2451
- const tracker = this.doubleTapTrackers.get(signalName);
2452
- if (tracker) {
2453
- const forceQuit = handleDoubleTap(tracker);
2454
- if (forceQuit) {
2455
- return;
2456
- }
2457
- }
2458
- }
2459
- await this.executeHandlers(signalName, sig);
2460
- };
2461
- process.on(signalName, listener);
2462
- this.nativeListeners.set(signalName, listener);
2463
- }
2464
- /**
2465
- * Execute all registered handlers for a signal
2466
- */
2467
- async executeHandlers(signalName, signal) {
2468
- const handlers = this.handlers.get(signalName);
2469
- if (!handlers || handlers.length === 0) {
2470
- return;
2471
- }
2472
- this.shuttingDown = true;
2473
- if (this.options.logger) {
2474
- this.options.logger.info(`Signal received: ${signalName}`, {
2475
- handler_count: handlers.length
2476
- });
2477
- }
2478
- for (const registration of handlers) {
2479
- try {
2480
- await this.executeWithTimeout(registration, signal);
2481
- } catch (error) {
2482
- if (this.options.logger) {
2483
- this.options.logger.warn(`Signal handler failed: ${signalName}`, {
2484
- handler_id: registration.id,
2485
- error: error instanceof Error ? error.message : String(error)
2486
- });
2487
- }
2488
- if (this.options.telemetry) {
2489
- this.options.telemetry.emit("fulmen.signal.handler_error", {
2490
- signal: signalName,
2491
- handler_id: registration.id,
2492
- error_type: error instanceof Error ? error.constructor.name : "unknown"
2493
- });
2494
- }
2495
- if (this.options.timeoutBehavior === "force_exit") {
2496
- const exitCode = (await getSignal(signalName))?.exit_code ?? 1;
2497
- if (!this.options.testMode) {
2498
- process.exit(exitCode);
2499
- }
2500
- return;
2501
- }
2502
- }
2503
- }
2504
- const tracker = this.doubleTapTrackers.get(signalName);
2505
- if (tracker) {
2506
- resetDoubleTap(tracker);
2507
- }
2508
- }
2509
- /**
2510
- * Execute a handler with timeout enforcement
2511
- */
2512
- async executeWithTimeout(registration, signal) {
2513
- const timeoutPromise = new Promise((_, reject) => {
2514
- setTimeout(() => {
2515
- reject(new Error(`Handler timeout exceeded: ${registration.timeoutMs}ms`));
2516
- }, registration.timeoutMs);
2517
- });
2518
- const handlerPromise = Promise.resolve(registration.handler(signal));
2519
- try {
2520
- await Promise.race([handlerPromise, timeoutPromise]);
2521
- if (this.options.telemetry) {
2522
- this.options.telemetry.emit("fulmen.signal.handler_completed", {
2523
- signal: registration.signal,
2524
- handler_id: registration.id
2525
- });
2526
- }
2527
- } catch (error) {
2528
- if (this.options.telemetry) {
2529
- this.options.telemetry.emit("fulmen.signal.handler_timeout", {
2530
- signal: registration.signal,
2531
- handler_id: registration.id,
2532
- timeout_ms: String(registration.timeoutMs)
2533
- });
2534
- }
2535
- throw error;
2536
- }
2537
- }
2538
- /**
2539
- * Unregister a signal handler
2540
- *
2541
- * @param signal - Signal name
2542
- * @param handler - Handler to remove (if not provided, removes all handlers)
2543
- */
2544
- unregister(signal, handler) {
2545
- const handlers = this.handlers.get(signal);
2546
- if (!handlers) {
2547
- return;
2548
- }
2549
- if (handler) {
2550
- const index = handlers.findIndex((h) => h.handler === handler);
2551
- if (index !== -1) {
2552
- handlers.splice(index, 1);
2553
- }
2554
- } else {
2555
- handlers.length = 0;
2556
- }
2557
- if (handlers.length === 0) {
2558
- const listener = this.nativeListeners.get(signal);
2559
- if (listener) {
2560
- process.off(signal, listener);
2561
- this.nativeListeners.delete(signal);
2562
- }
2563
- this.handlers.delete(signal);
2564
- }
2565
- }
2566
- /**
2567
- * Check if a signal has registered handlers
2568
- */
2569
- isRegistered(signal) {
2570
- const handlers = this.handlers.get(signal);
2571
- return handlers !== void 0 && handlers.length > 0;
2572
- }
2573
- /**
2574
- * Get handler count for a signal
2575
- */
2576
- getHandlerCount(signal) {
2577
- const handlers = this.handlers.get(signal);
2578
- return handlers?.length ?? 0;
2579
- }
2580
- /**
2581
- * Trigger signal handlers manually (for testing)
2582
- *
2583
- * @param signal - Signal name
2584
- */
2585
- async trigger(signal) {
2586
- const handlers = this.handlers.get(signal);
2587
- if (!handlers || handlers.length === 0) {
2588
- return;
2589
- }
2590
- await this.executeHandlers(signal, signal);
2591
- }
2592
- /**
2593
- * Shutdown the signal manager and cleanup all handlers
2594
- */
2595
- async shutdown() {
2596
- for (const [signal, listener] of this.nativeListeners.entries()) {
2597
- process.off(signal, listener);
2598
- }
2599
- this.nativeListeners.clear();
2600
- this.handlers.clear();
2601
- this.doubleTapTrackers.clear();
2602
- this.shuttingDown = false;
2603
- }
2604
- /**
2605
- * Check if manager is currently shutting down
2606
- */
2607
- isShuttingDown() {
2608
- return this.shuttingDown;
2609
- }
2610
- };
2611
- }
2612
- });
2613
-
2614
- // src/foundry/signals/reload.ts
2615
- function createConfigReloadHandler(options) {
2616
- const {
2617
- loader,
2618
- validator,
2619
- onValidated,
2620
- exitCode = 129,
2621
- logger,
2622
- telemetry,
2623
- testMode = false
2624
- } = options;
2625
- return async () => {
2626
- if (logger) {
2627
- logger.info("Config reload requested (SIGHUP)");
2628
- }
2629
- if (telemetry) {
2630
- telemetry.emit("fulmen.signal.config_reload_requested", {
2631
- signal: "SIGHUP"
2632
- });
2633
- }
2634
- try {
2635
- const newConfig = await loader();
2636
- const result = await validator(newConfig);
2637
- if (!result.valid) {
2638
- if (logger) {
2639
- logger.warn("Config validation failed - continuing with current config", {
2640
- error_count: result.errors?.length ?? 0,
2641
- errors: result.errors
2642
- });
2643
- }
2644
- if (telemetry) {
2645
- telemetry.emit("fulmen.signal.config_reload_rejected", {
2646
- signal: "SIGHUP",
2647
- reason: "validation_failed",
2648
- error_count: String(result.errors?.length ?? 0)
2649
- });
2650
- }
2651
- return;
2652
- }
2653
- if (logger) {
2654
- logger.info("Config validation succeeded - exiting for restart");
2655
- }
2656
- if (telemetry) {
2657
- telemetry.emit("fulmen.signal.config_reload_accepted", {
2658
- signal: "SIGHUP"
2659
- });
2660
- }
2661
- if (onValidated) {
2662
- await onValidated(newConfig);
2663
- }
2664
- if (!testMode) {
2665
- process.exit(exitCode);
2666
- }
2667
- } catch (error) {
2668
- if (logger) {
2669
- logger.warn("Config reload failed with error - continuing with current config", {
2670
- error: error instanceof Error ? error.message : String(error)
2671
- });
2672
- }
2673
- if (telemetry) {
2674
- telemetry.emit("fulmen.signal.config_reload_error", {
2675
- signal: "SIGHUP",
2676
- error_type: error instanceof Error ? error.constructor.name : "unknown"
2677
- });
2678
- }
2679
- }
2680
- };
2681
- }
2682
- var ConfigReloadTracker;
2683
- var init_reload = __esm({
2684
- "src/foundry/signals/reload.ts"() {
2685
- ConfigReloadTracker = class {
2686
- failures = 0;
2687
- lastFailureTime = null;
2688
- maxFailures;
2689
- logger;
2690
- telemetry;
2691
- constructor(options) {
2692
- this.maxFailures = options.maxFailures ?? 3;
2693
- this.logger = options.logger;
2694
- this.telemetry = options.telemetry;
2695
- }
2696
- /**
2697
- * Record a reload failure
2698
- *
2699
- * @returns true if threshold exceeded, false otherwise
2700
- */
2701
- recordFailure() {
2702
- this.failures++;
2703
- this.lastFailureTime = Date.now();
2704
- if (this.failures >= this.maxFailures) {
2705
- if (this.logger) {
2706
- this.logger.warn(
2707
- `${this.failures} consecutive config reload failures - check config source`,
2708
- {
2709
- failure_count: this.failures,
2710
- threshold: this.maxFailures
2711
- }
2712
- );
2713
- }
2714
- if (this.telemetry) {
2715
- this.telemetry.emit("fulmen.signal.config_reload_threshold_exceeded", {
2716
- failure_count: String(this.failures),
2717
- threshold: String(this.maxFailures)
2718
- });
2719
- }
2720
- return true;
2721
- }
2722
- return false;
2723
- }
2724
- /**
2725
- * Record a successful reload (resets counter)
2726
- */
2727
- recordSuccess() {
2728
- this.failures = 0;
2729
- this.lastFailureTime = null;
2730
- }
2731
- /**
2732
- * Get current failure count
2733
- */
2734
- getFailureCount() {
2735
- return this.failures;
2736
- }
2737
- /**
2738
- * Get last failure timestamp
2739
- */
2740
- getLastFailureTime() {
2741
- return this.lastFailureTime;
2742
- }
2743
- };
2744
- }
2745
- });
2746
-
2747
- // src/foundry/signals/index.ts
2748
- var init_signals = __esm({
2749
- "src/foundry/signals/index.ts"() {
2750
- init_capabilities();
2751
- init_catalog();
2752
- init_config_reload_endpoint();
2753
- init_control_discovery_endpoint();
2754
- init_convenience();
2755
- init_double_tap();
2756
- init_guards();
2757
- init_http_helper();
2758
- init_manager();
2759
- init_reload();
2760
- init_windows();
2761
- }
2762
- });
2763
-
2764
- // src/signals/index.ts
2765
- init_signals();
2766
-
2767
- export { ConfigReloadTracker, SignalManager, createBearerTokenAuth, createConfigReloadEndpoint, createConfigReloadHandler, createControlDiscoveryEndpoint, createDoubleTapTracker, createSignalEndpoint, createSignalManager, createSimpleRateLimiter, ensurePOSIX, ensureSignalExitCodesSupported, ensureSupported, ensureWindows, getBehavior, getFallbackMetadata, getHttpFallbackGuidance, getPlatform, getPlatformCapabilities, getSignal, getSignalCatalog, getSignalNumber, getSignalsVersion, getWindowTimeRemaining, getWindowsEvent, handleDoubleTap, handleWindowsFallback, isPOSIX, isWindows, isWithinWindow, listBehaviors, listSignals, onAnyShutdown, onEmergencyQuit, onReload, onShutdown, onUSR1, onUSR2, requiresFallback, resetDoubleTap, supportsSignal, supportsSignalExitCodes };
1
+ export { ConfigReloadTracker, SignalManager, createBearerTokenAuth, createConfigReloadEndpoint, createConfigReloadHandler, createControlDiscoveryEndpoint, createDoubleTapTracker, createSignalEndpoint, createSignalManager, createSimpleRateLimiter, ensurePOSIX, ensureSignalExitCodesSupported, ensureSupported, ensureWindows, getFallbackMetadata, getHttpFallbackGuidance, getWindowTimeRemaining, handleDoubleTap, handleWindowsFallback, isWithinWindow, onAnyShutdown, onEmergencyQuit, onReload, onShutdown, onUSR1, onUSR2, requiresFallback, resetDoubleTap } from '../chunk-HS4F3BRR.js';
2
+ export { getBehavior, getPlatform, getPlatformCapabilities, getSignal, getSignalCatalog, getSignalNumber, getSignalsVersion, getWindowsEvent, isPOSIX, isWindows, listBehaviors, listSignals, supportsSignal, supportsSignalExitCodes } from '../chunk-HIBZ2AY7.js';
3
+ import '../chunk-HY6JQOFN.js';
4
+ import '../chunk-3HISR2XI.js';
5
+ import '../chunk-7SVYOPJ3.js';
6
+ import '../chunk-QCCN6POA.js';
7
+ import '../chunk-BTUS6PZZ.js';
8
+ import '../chunk-UP2VWCW5.js';
2768
9
  //# sourceMappingURL=index.js.map
2769
10
  //# sourceMappingURL=index.js.map