@fulmenhq/tsfulmen 0.2.0 → 0.2.3

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 (111) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +61 -7
  3. package/config/crucible-ts/agentic/roles/README.md +3 -3
  4. package/config/crucible-ts/library/fulencode/fixtures/README.md +18 -0
  5. package/config/crucible-ts/library/fulencode/fixtures/bom/bom.yaml +14 -0
  6. package/config/crucible-ts/library/fulencode/fixtures/detection/detection.yaml +12 -0
  7. package/config/crucible-ts/library/fulencode/fixtures/invalid-encodings/base64.yaml +10 -0
  8. package/config/crucible-ts/library/fulencode/fixtures/normalization/text-safe.yaml +10 -0
  9. package/config/crucible-ts/library/fulencode/fixtures/telemetry/telemetry-test-cases.yaml +24 -0
  10. package/config/crucible-ts/library/fulencode/fixtures/valid-encodings/base64.yaml +11 -0
  11. package/config/crucible-ts/taxonomy/library/platform-modules/v1.0.0/modules.yaml +2 -2
  12. package/config/crucible-ts/taxonomy/metrics.yaml +79 -1
  13. package/dist/appidentity/index.d.ts +31 -109
  14. package/dist/appidentity/index.js +369 -60
  15. package/dist/appidentity/index.js.map +1 -1
  16. package/dist/config/index.d.ts +46 -1
  17. package/dist/config/index.js +427 -62
  18. package/dist/config/index.js.map +1 -1
  19. package/dist/crucible/index.js +367 -59
  20. package/dist/crucible/index.js.map +1 -1
  21. package/dist/errors/index.d.ts +1 -1
  22. package/dist/errors/index.js +367 -59
  23. package/dist/errors/index.js.map +1 -1
  24. package/dist/foundry/index.d.ts +2 -1
  25. package/dist/foundry/index.js +368 -60
  26. package/dist/foundry/index.js.map +1 -1
  27. package/dist/fulencode/index.d.ts +102 -0
  28. package/dist/fulencode/index.js +806 -0
  29. package/dist/fulencode/index.js.map +1 -0
  30. package/dist/index.d.ts +4 -3
  31. package/dist/index.js +370 -61
  32. package/dist/index.js.map +1 -1
  33. package/dist/pathfinder/index.d.ts +1 -1
  34. package/dist/pathfinder/index.js +367 -59
  35. package/dist/pathfinder/index.js.map +1 -1
  36. package/dist/reports/license-inventory.csv +31 -24
  37. package/dist/schema/index.d.ts +16 -3
  38. package/dist/schema/index.js +368 -60
  39. package/dist/schema/index.js.map +1 -1
  40. package/dist/signals/index.d.ts +483 -395
  41. package/dist/signals/index.js +368 -60
  42. package/dist/signals/index.js.map +1 -1
  43. package/dist/telemetry/http/index.js +368 -59
  44. package/dist/telemetry/http/index.js.map +1 -1
  45. package/dist/telemetry/index.d.ts +1 -1
  46. package/dist/telemetry/index.js +367 -59
  47. package/dist/telemetry/index.js.map +1 -1
  48. package/dist/telemetry/prometheus/index.d.ts +1 -1
  49. package/dist/telemetry/prometheus/index.js +369 -59
  50. package/dist/telemetry/prometheus/index.js.map +1 -1
  51. package/dist/{types-BJswWpQC.d.ts → types-DdoeE7F5.d.ts} +1 -1
  52. package/dist/types-Dv5TERCM.d.ts +108 -0
  53. package/package.json +13 -8
  54. package/schemas/crucible-ts/library/fulencode/v1.0.0/README.md +37 -0
  55. package/schemas/crucible-ts/library/fulencode/v1.0.0/bom-result.schema.json +48 -0
  56. package/schemas/crucible-ts/library/fulencode/v1.0.0/decode-options.schema.json +60 -0
  57. package/schemas/crucible-ts/library/fulencode/v1.0.0/decoding-result.schema.json +70 -0
  58. package/schemas/crucible-ts/library/fulencode/v1.0.0/detect-options.schema.json +25 -0
  59. package/schemas/crucible-ts/library/fulencode/v1.0.0/detection-result.schema.json +57 -0
  60. package/schemas/crucible-ts/library/fulencode/v1.0.0/encode-options.schema.json +71 -0
  61. package/schemas/crucible-ts/library/fulencode/v1.0.0/encoding-result.schema.json +57 -0
  62. package/schemas/crucible-ts/library/fulencode/v1.0.0/fulencode-config.schema.json +8 -4
  63. package/schemas/crucible-ts/library/fulencode/v1.0.0/fulencode-error.schema.json +66 -0
  64. package/schemas/crucible-ts/library/fulencode/v1.0.0/normalization-result.schema.json +73 -0
  65. package/schemas/crucible-ts/library/fulencode/v1.0.0/normalize-options.schema.json +44 -0
  66. package/schemas/crucible-ts/meta/README.md +38 -2
  67. package/schemas/crucible-ts/meta/draft-04/schema.json +222 -0
  68. package/schemas/crucible-ts/meta/draft-06/schema.json +218 -0
  69. package/schemas/crucible-ts/meta/draft-2019-09/meta/applicator.json +93 -0
  70. package/schemas/crucible-ts/meta/draft-2019-09/meta/content.json +21 -0
  71. package/schemas/crucible-ts/meta/draft-2019-09/meta/core.json +58 -0
  72. package/schemas/crucible-ts/meta/draft-2019-09/meta/format.json +15 -0
  73. package/schemas/crucible-ts/meta/draft-2019-09/meta/meta-data.json +35 -0
  74. package/schemas/crucible-ts/meta/draft-2019-09/meta/validation.json +119 -0
  75. package/schemas/crucible-ts/meta/draft-2019-09/offline.schema.json +148 -0
  76. package/schemas/crucible-ts/meta/draft-2019-09/schema.json +62 -0
  77. package/schemas/crucible-ts/meta/fixtures/draft-04-sample.json +16 -0
  78. package/schemas/crucible-ts/meta/fixtures/draft-06-sample.json +16 -0
  79. package/schemas/crucible-ts/meta/fixtures/draft-07-sample.json +34 -0
  80. package/schemas/crucible-ts/meta/fixtures/draft-2019-09-sample.json +21 -0
  81. package/schemas/crucible-ts/meta/fixtures/draft-2020-12-sample.json +21 -0
  82. package/schemas/crucible-ts/taxonomy/library/fulencode/normalization-profiles/v1.0.0/profiles.yaml +16 -0
  83. package/schemas/crucible-ts/upstream/3leaps/crucible/PROVENANCE.md +64 -0
  84. package/schemas/crucible-ts/upstream/3leaps/crucible/config/classifiers/dimensions/access-tier.dimension.json +103 -0
  85. package/schemas/crucible-ts/upstream/3leaps/crucible/config/classifiers/dimensions/retention-lifecycle.dimension.json +103 -0
  86. package/schemas/crucible-ts/upstream/3leaps/crucible/config/classifiers/dimensions/schema-stability.dimension.json +100 -0
  87. package/schemas/crucible-ts/upstream/3leaps/crucible/config/classifiers/dimensions/sensitivity.dimension.json +130 -0
  88. package/schemas/crucible-ts/upstream/3leaps/crucible/config/classifiers/dimensions/velocity-mode.dimension.json +79 -0
  89. package/schemas/crucible-ts/upstream/3leaps/crucible/config/classifiers/dimensions/volatility.dimension.json +72 -0
  90. package/schemas/crucible-ts/upstream/3leaps/crucible/config/classifiers/dimensions/volume-tier.dimension.json +66 -0
  91. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/catalog/classifiers/README.md +29 -0
  92. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/access-tier-classification.md +163 -0
  93. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/classifiers-framework.md +157 -0
  94. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/data-sensitivity-classification.md +259 -0
  95. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/retention-lifecycle-classification.md +200 -0
  96. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/schema-stability-classification.md +205 -0
  97. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/velocity-mode-classification.md +222 -0
  98. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/volatility-classification.md +209 -0
  99. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/volume-tier-classification.md +200 -0
  100. package/schemas/crucible-ts/upstream/3leaps/crucible/schemas/ailink/v0/README.md +48 -0
  101. package/schemas/crucible-ts/upstream/3leaps/{ailink → crucible/schemas/ailink}/v0/prompt.schema.json +4 -18
  102. package/schemas/crucible-ts/upstream/3leaps/{ailink → crucible/schemas/ailink}/v0/search-response.schema.json +7 -37
  103. package/schemas/crucible-ts/upstream/3leaps/crucible/schemas/classifiers/v0/dimension-definition.schema.json +247 -0
  104. package/schemas/crucible-ts/upstream/3leaps/crucible/schemas/classifiers/v0/sensitivity-level.schema.json +67 -0
  105. package/schemas/crucible-ts/upstream/3leaps/crucible/schemas/foundation/v0/error-response.schema.json +59 -0
  106. package/schemas/crucible-ts/upstream/3leaps/crucible/schemas/foundation/v0/lifecycle-phases.data.json +102 -0
  107. package/schemas/crucible-ts/upstream/3leaps/crucible/schemas/foundation/v0/lifecycle-phases.schema.json +101 -0
  108. package/schemas/crucible-ts/upstream/3leaps/crucible/schemas/foundation/v0/release-phase.schema.json +18 -0
  109. package/schemas/crucible-ts/upstream/3leaps/crucible/schemas/foundation/v0/types.schema.json +177 -0
  110. package/schemas/crucible-ts/upstream/3leaps/PROVENANCE.md +0 -43
  111. /package/schemas/crucible-ts/upstream/3leaps/{agentic → crucible/schemas/agentic}/v0/role-prompt.schema.json +0 -0
@@ -1,3 +1,4 @@
1
+ import addFormats from 'ajv-formats';
1
2
  import { spawn } from 'child_process';
2
3
  import { readFile, access, mkdir, writeFile } from 'fs/promises';
3
4
  import { parse, stringify } from 'yaml';
@@ -5,7 +6,9 @@ import { join, dirname, extname, relative, isAbsolute, resolve } from 'path';
5
6
  import { fileURLToPath } from 'url';
6
7
  import glob from 'fast-glob';
7
8
  import Ajv from 'ajv';
8
- import addFormats from 'ajv-formats';
9
+ import Ajv2019 from 'ajv/dist/2019';
10
+ import Ajv2020 from 'ajv/dist/2020';
11
+ import AjvDraft04 from 'ajv-draft-04';
9
12
  import { Readable } from 'stream';
10
13
  import picomatch from 'picomatch';
11
14
  import { suggest as suggest$1, substringSimilarity, score as score$1, normalize as normalize$1, jaro_winkler, damerau_levenshtein, osa_distance, levenshtein } from '@3leaps/string-metrics-wasm';
@@ -22,6 +25,27 @@ var __export = (target, all) => {
22
25
  for (var name in all)
23
26
  __defProp(target, name, { get: all[name], enumerable: true });
24
27
  };
28
+ function applyFulmenAjvFormats(ajv, options = {}) {
29
+ const mode = options.mode ?? "fast";
30
+ const formats = options.formats ?? DEFAULT_FORMATS;
31
+ addFormats(ajv, { mode, formats });
32
+ return ajv;
33
+ }
34
+ var DEFAULT_FORMATS;
35
+ var init_ajv_formats = __esm({
36
+ "src/schema/ajv-formats.ts"() {
37
+ DEFAULT_FORMATS = [
38
+ "date-time",
39
+ "email",
40
+ "hostname",
41
+ "ipv4",
42
+ "ipv6",
43
+ "uri",
44
+ "uri-reference",
45
+ "uuid"
46
+ ];
47
+ }
48
+ });
25
49
 
26
50
  // src/schema/errors.ts
27
51
  var errors_exports = {};
@@ -1474,20 +1498,14 @@ async function loadMetaSchema(draft) {
1474
1498
  const content = await readFile(metaSchemaPath, "utf-8");
1475
1499
  return JSON.parse(content);
1476
1500
  }
1477
- async function loadVocabularySchemas() {
1501
+ async function loadVocabularySchemas(draft) {
1502
+ if (draft !== "draft-2019-09" && draft !== "draft-2020-12") {
1503
+ return [];
1504
+ }
1478
1505
  const __filename3 = fileURLToPath(import.meta.url);
1479
1506
  const __dirname4 = dirname(__filename3);
1480
- const vocabDir = join(
1481
- __dirname4,
1482
- "..",
1483
- "..",
1484
- "schemas",
1485
- "crucible-ts",
1486
- "meta",
1487
- "draft-2020-12",
1488
- "meta"
1489
- );
1490
- const vocabFiles = [
1507
+ const vocabDir = join(__dirname4, "..", "..", "schemas", "crucible-ts", "meta", draft, "meta");
1508
+ const vocabFiles = draft === "draft-2020-12" ? [
1491
1509
  "core.json",
1492
1510
  "applicator.json",
1493
1511
  "unevaluated.json",
@@ -1495,6 +1513,13 @@ async function loadVocabularySchemas() {
1495
1513
  "meta-data.json",
1496
1514
  "format-annotation.json",
1497
1515
  "content.json"
1516
+ ] : [
1517
+ "core.json",
1518
+ "applicator.json",
1519
+ "validation.json",
1520
+ "meta-data.json",
1521
+ "format.json",
1522
+ "content.json"
1498
1523
  ];
1499
1524
  const schemas = [];
1500
1525
  for (const file of vocabFiles) {
@@ -1549,47 +1574,65 @@ async function loadReferencedSchema(uri) {
1549
1574
  }
1550
1575
  return JSON.parse(content);
1551
1576
  }
1552
- function getAjv() {
1553
- if (!ajvInstance) {
1554
- ajvInstance = new Ajv({
1555
- strict: false,
1556
- allErrors: true,
1557
- verbose: true,
1558
- // Allow schemas with $id to be added without replacing existing ones
1559
- addUsedSchema: false,
1560
- // Enable async schema loading for YAML references
1561
- loadSchema: loadReferencedSchema
1562
- });
1563
- addFormats(ajvInstance, {
1564
- mode: "fast",
1565
- formats: ["date-time", "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference"]
1566
- });
1567
- metaschemaReady = Promise.all([loadVocabularySchemas(), loadMetaSchema("draft-2020-12")]).then(([vocabSchemas, metaSchema]) => {
1568
- if (ajvInstance) {
1569
- for (const vocabSchema of vocabSchemas) {
1570
- try {
1571
- ajvInstance.addMetaSchema(vocabSchema);
1572
- } catch {
1573
- }
1574
- }
1575
- ajvInstance.addMetaSchema(metaSchema);
1576
- }
1577
- }).catch((error) => {
1578
- throw new Error(`Failed to load metaschemas: ${error}`);
1579
- });
1577
+ function detectDialect(schema) {
1578
+ if (schema && typeof schema === "object" && !Array.isArray(schema)) {
1579
+ const maybeSchema = schema;
1580
+ const declared = maybeSchema.$schema;
1581
+ if (typeof declared === "string") {
1582
+ if (declared.includes("draft-04")) return "draft-04";
1583
+ if (declared.includes("draft-06")) return "draft-06";
1584
+ if (declared.includes("draft-07")) return "draft-07";
1585
+ if (declared.includes("draft/2019-09")) return "draft-2019-09";
1586
+ if (declared.includes("draft/2020-12")) return "draft-2020-12";
1587
+ }
1580
1588
  }
1581
- return ajvInstance;
1589
+ return "draft-2020-12";
1590
+ }
1591
+ function createAjv(dialect) {
1592
+ const AjvCtor = dialect === "draft-2020-12" ? Ajv2020 : dialect === "draft-2019-09" ? Ajv2019 : dialect === "draft-04" ? AjvDraft04 : Ajv;
1593
+ const ajv = new AjvCtor({
1594
+ strict: false,
1595
+ allErrors: true,
1596
+ verbose: true,
1597
+ // Allow schemas with $id to be added without replacing existing ones
1598
+ addUsedSchema: false,
1599
+ // draft-04 uses "id"; later drafts use "$id"
1600
+ schemaId: dialect === "draft-04" ? "id" : "$id",
1601
+ // Enable async schema loading for YAML references
1602
+ loadSchema: loadReferencedSchema
1603
+ });
1604
+ applyFulmenAjvFormats(ajv);
1605
+ return ajv;
1606
+ }
1607
+ async function getAjv(dialect) {
1608
+ const existing = ajvInstances.get(dialect);
1609
+ if (existing) {
1610
+ const ready = metaschemaReady.get(dialect);
1611
+ if (ready) await ready;
1612
+ return existing;
1613
+ }
1614
+ const ajv = createAjv(dialect);
1615
+ ajvInstances.set(dialect, ajv);
1616
+ const readyPromise = Promise.all([loadVocabularySchemas(dialect), loadMetaSchema(dialect)]).then(([vocabSchemas, metaSchema]) => {
1617
+ for (const vocabSchema of vocabSchemas) {
1618
+ try {
1619
+ ajv.addMetaSchema(vocabSchema);
1620
+ } catch {
1621
+ }
1622
+ }
1623
+ try {
1624
+ ajv.addMetaSchema(metaSchema);
1625
+ } catch {
1626
+ }
1627
+ }).catch((error) => {
1628
+ throw new Error(`Failed to load metaschemas (${dialect}): ${error}`);
1629
+ });
1630
+ metaschemaReady.set(dialect, readyPromise);
1631
+ await readyPromise;
1632
+ return ajv;
1582
1633
  }
1583
1634
  async function compileSchema(schema, options = {}) {
1584
- const ajv = getAjv();
1585
- if (metaschemaReady) {
1586
- await metaschemaReady;
1587
- }
1588
- const cacheKey = typeof schema === "string" ? schema : JSON.stringify(schema);
1589
- const cached = schemaCache.get(cacheKey);
1590
- if (cached !== void 0) {
1591
- return cached;
1592
- }
1635
+ const baseKey = typeof schema === "string" ? schema : JSON.stringify(schema);
1593
1636
  let parsedSchema;
1594
1637
  if (typeof schema === "string") {
1595
1638
  try {
@@ -1607,18 +1650,27 @@ async function compileSchema(schema, options = {}) {
1607
1650
  } else {
1608
1651
  parsedSchema = schema;
1609
1652
  }
1653
+ const dialect = detectDialect(parsedSchema);
1654
+ const ajv = await getAjv(dialect);
1655
+ const cacheKey = `${dialect}:${baseKey}`;
1656
+ const cached = schemaCache.get(cacheKey);
1657
+ if (cached !== void 0) {
1658
+ return cached;
1659
+ }
1610
1660
  try {
1611
1661
  if (options.aliases && options.aliases.length > 0) {
1612
1662
  for (const alias of options.aliases) {
1613
1663
  if (alias && ajv.getSchema(alias) === void 0) {
1614
1664
  try {
1615
- ajv.addSchema(parsedSchema, alias);
1665
+ if (typeof parsedSchema === "object" && parsedSchema !== null) {
1666
+ ajv.addSchema(parsedSchema, alias);
1667
+ }
1616
1668
  } catch {
1617
1669
  }
1618
1670
  }
1619
1671
  }
1620
1672
  }
1621
- const validator = await ajv.compileAsync(parsedSchema);
1673
+ const validator = typeof parsedSchema === "boolean" ? ajv.compile(parsedSchema) : await ajv.compileAsync(parsedSchema);
1622
1674
  schemaCache.set(cacheKey, validator);
1623
1675
  return validator;
1624
1676
  } catch (error) {
@@ -1688,8 +1740,39 @@ async function validateFile(filePath, validator) {
1688
1740
  }
1689
1741
  async function validateSchema(schema) {
1690
1742
  try {
1691
- const validator = await compileSchema(schema);
1692
- validateData({}, validator);
1743
+ let parsedSchema;
1744
+ if (typeof schema === "string") {
1745
+ try {
1746
+ parsedSchema = JSON.parse(schema);
1747
+ } catch {
1748
+ parsedSchema = parse(schema);
1749
+ }
1750
+ } else if (Buffer.isBuffer(schema)) {
1751
+ const content = schema.toString("utf-8");
1752
+ try {
1753
+ parsedSchema = JSON.parse(content);
1754
+ } catch {
1755
+ parsedSchema = parse(content);
1756
+ }
1757
+ } else {
1758
+ parsedSchema = schema;
1759
+ }
1760
+ const dialect = detectDialect(parsedSchema);
1761
+ const ajv = await getAjv(dialect);
1762
+ const metaValid = ajv.validateSchema(parsedSchema);
1763
+ if (!metaValid && ajv.errors) {
1764
+ const diagnostics = ajv.errors.map(
1765
+ (error) => createDiagnostic(
1766
+ error.instancePath || "",
1767
+ error.message || "Schema meta-validation failed",
1768
+ error.keyword || "unknown",
1769
+ "ERROR",
1770
+ "ajv"
1771
+ )
1772
+ );
1773
+ return { valid: false, diagnostics, source: "ajv" };
1774
+ }
1775
+ await compileSchema(parsedSchema);
1693
1776
  return {
1694
1777
  valid: true,
1695
1778
  diagnostics: [],
@@ -1760,14 +1843,16 @@ async function validateFileBySchemaId(filePath, schemaId, registryOptions) {
1760
1843
  throw error;
1761
1844
  }
1762
1845
  }
1763
- var ajvInstance, metaschemaReady, schemaCache;
1846
+ var ajvInstances, metaschemaReady, schemaCache;
1764
1847
  var init_validator = __esm({
1765
1848
  "src/schema/validator.ts"() {
1766
1849
  init_telemetry();
1850
+ init_ajv_formats();
1767
1851
  init_errors();
1768
1852
  init_registry();
1769
1853
  init_utils();
1770
- metaschemaReady = null;
1854
+ ajvInstances = /* @__PURE__ */ new Map();
1855
+ metaschemaReady = /* @__PURE__ */ new Map();
1771
1856
  schemaCache = /* @__PURE__ */ new Map();
1772
1857
  }
1773
1858
  });
@@ -3722,6 +3807,224 @@ var init_capabilities2 = __esm({
3722
3807
  }
3723
3808
  });
3724
3809
 
3810
+ // src/foundry/signals/config-reload-endpoint.ts
3811
+ function createConfigReloadEndpoint(options) {
3812
+ const { loader, validator, onReload: onReload2, auth, rateLimit, logger, telemetry } = options;
3813
+ return async (payload, req) => {
3814
+ const correlationId = payload.correlation_id ?? generateCorrelationId();
3815
+ const authResult = await auth(req);
3816
+ if (!authResult.authenticated) {
3817
+ if (logger) {
3818
+ logger.warn("Config reload endpoint: authentication failed", {
3819
+ correlation_id: correlationId,
3820
+ reason: authResult.reason
3821
+ });
3822
+ }
3823
+ if (telemetry) {
3824
+ telemetry.emit("fulmen.config.http_endpoint.auth_failed", {
3825
+ correlation_id: correlationId
3826
+ });
3827
+ }
3828
+ return {
3829
+ status: "error",
3830
+ error: "authentication_failed",
3831
+ message: authResult.reason || "Authentication required",
3832
+ statusCode: 401
3833
+ };
3834
+ }
3835
+ const identity = authResult.identity || "unknown";
3836
+ if (rateLimit) {
3837
+ const rateLimitResult = await rateLimit(identity);
3838
+ if (!rateLimitResult.allowed) {
3839
+ if (logger) {
3840
+ logger.warn("Config reload endpoint: rate limit exceeded", {
3841
+ correlation_id: correlationId,
3842
+ identity
3843
+ });
3844
+ }
3845
+ if (telemetry) {
3846
+ telemetry.emit("fulmen.config.http_endpoint.rate_limited", {
3847
+ correlation_id: correlationId
3848
+ });
3849
+ }
3850
+ return {
3851
+ status: "error",
3852
+ error: "rate_limit_exceeded",
3853
+ message: "Rate limit exceeded. Please try again later.",
3854
+ statusCode: 429
3855
+ };
3856
+ }
3857
+ }
3858
+ if (telemetry) {
3859
+ telemetry.emit("fulmen.config.http_endpoint.reload_requested", {
3860
+ correlation_id: correlationId
3861
+ });
3862
+ }
3863
+ try {
3864
+ const config = await loader();
3865
+ if (validator) {
3866
+ const validation = await validator(config);
3867
+ if (!validation.valid) {
3868
+ if (logger) {
3869
+ logger.warn("Config reload endpoint: validation failed", {
3870
+ correlation_id: correlationId,
3871
+ error_count: validation.errors?.length ?? 0
3872
+ });
3873
+ }
3874
+ if (telemetry) {
3875
+ telemetry.emit("fulmen.config.http_endpoint.reload_rejected", {
3876
+ correlation_id: correlationId,
3877
+ reason: "validation_failed"
3878
+ });
3879
+ }
3880
+ return {
3881
+ status: "error",
3882
+ error: "validation_failed",
3883
+ message: "Configuration validation failed",
3884
+ validation_errors: validation.errors,
3885
+ statusCode: 422
3886
+ };
3887
+ }
3888
+ }
3889
+ if (onReload2) {
3890
+ await onReload2(config);
3891
+ }
3892
+ if (telemetry) {
3893
+ telemetry.emit("fulmen.config.http_endpoint.reload_accepted", {
3894
+ correlation_id: correlationId
3895
+ });
3896
+ }
3897
+ if (logger) {
3898
+ logger.info("Config reload endpoint: reload accepted", {
3899
+ correlation_id: correlationId,
3900
+ reason: payload.reason
3901
+ });
3902
+ }
3903
+ return {
3904
+ status: "reloaded",
3905
+ correlation_id: correlationId,
3906
+ message: "Configuration reloaded",
3907
+ statusCode: 200
3908
+ };
3909
+ } catch (error) {
3910
+ if (logger) {
3911
+ logger.warn("Config reload endpoint: reload failed", {
3912
+ correlation_id: correlationId,
3913
+ error: error instanceof Error ? error.message : String(error)
3914
+ });
3915
+ }
3916
+ if (telemetry) {
3917
+ telemetry.emit("fulmen.config.http_endpoint.reload_error", {
3918
+ correlation_id: correlationId,
3919
+ error_type: error instanceof Error ? error.constructor.name : "unknown"
3920
+ });
3921
+ }
3922
+ return {
3923
+ status: "error",
3924
+ error: "reload_failed",
3925
+ message: error instanceof Error ? error.message : String(error),
3926
+ statusCode: 500
3927
+ };
3928
+ }
3929
+ };
3930
+ }
3931
+ function generateCorrelationId() {
3932
+ return `cfg-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
3933
+ }
3934
+ var init_config_reload_endpoint = __esm({
3935
+ "src/foundry/signals/config-reload-endpoint.ts"() {
3936
+ }
3937
+ });
3938
+
3939
+ // src/appidentity/runtime.ts
3940
+ function detectRuntime() {
3941
+ const versions = process.versions;
3942
+ if (typeof versions.bun === "string" && versions.bun.length > 0) {
3943
+ return { name: "bun", version: versions.bun };
3944
+ }
3945
+ if (typeof versions.node === "string" && versions.node.length > 0) {
3946
+ return { name: "node", version: versions.node };
3947
+ }
3948
+ return { name: "unknown" };
3949
+ }
3950
+ function buildRuntimeInfo(options = {}) {
3951
+ const runtime = detectRuntime();
3952
+ const serviceName = options.serviceName ?? options.identity?.app.binary_name ?? "unknown-service";
3953
+ const vendor = options.vendor ?? options.identity?.app.vendor;
3954
+ return {
3955
+ service: {
3956
+ name: serviceName,
3957
+ vendor,
3958
+ version: options.version
3959
+ },
3960
+ runtime,
3961
+ platform: {
3962
+ os: process.platform,
3963
+ arch: process.arch
3964
+ }
3965
+ };
3966
+ }
3967
+ var init_runtime = __esm({
3968
+ "src/appidentity/runtime.ts"() {
3969
+ }
3970
+ });
3971
+
3972
+ // src/foundry/signals/control-discovery-endpoint.ts
3973
+ function createControlDiscoveryEndpoint(options) {
3974
+ const { identity, version, endpoints, auth, authSummary, logger, telemetry } = options;
3975
+ return async (req) => {
3976
+ if (auth) {
3977
+ const authResult = await auth(req);
3978
+ if (!authResult.authenticated) {
3979
+ if (logger) {
3980
+ logger.warn("Control discovery endpoint: authentication failed", {
3981
+ reason: authResult.reason
3982
+ });
3983
+ }
3984
+ if (telemetry) {
3985
+ telemetry.emit("fulmen.control.discovery.auth_failed", {
3986
+ service: identity.app.binary_name
3987
+ });
3988
+ }
3989
+ return {
3990
+ status: "error",
3991
+ error: "authentication_failed",
3992
+ message: authResult.reason || "Authentication required",
3993
+ statusCode: 401
3994
+ };
3995
+ }
3996
+ }
3997
+ if (telemetry) {
3998
+ telemetry.emit("fulmen.control.discovery.served", {
3999
+ service: identity.app.binary_name
4000
+ });
4001
+ }
4002
+ const runtime = buildRuntimeInfo({ identity, version });
4003
+ return {
4004
+ status: "ok",
4005
+ service: {
4006
+ name: identity.app.binary_name,
4007
+ vendor: identity.app.vendor,
4008
+ version
4009
+ },
4010
+ runtime: {
4011
+ name: runtime.runtime.name,
4012
+ version: runtime.runtime.version,
4013
+ platform: runtime.platform.os,
4014
+ arch: runtime.platform.arch
4015
+ },
4016
+ auth_summary: authSummary,
4017
+ endpoints,
4018
+ statusCode: 200
4019
+ };
4020
+ };
4021
+ }
4022
+ var init_control_discovery_endpoint = __esm({
4023
+ "src/foundry/signals/control-discovery-endpoint.ts"() {
4024
+ init_runtime();
4025
+ }
4026
+ });
4027
+
3725
4028
  // src/foundry/signals/convenience.ts
3726
4029
  async function onShutdown(manager, handler, options = {}) {
3727
4030
  await manager.register("SIGTERM", handler, options);
@@ -3981,7 +4284,7 @@ var init_guards = __esm({
3981
4284
  function createSignalEndpoint(options) {
3982
4285
  const { manager, auth, rateLimit, logger, telemetry, allowedSignals } = options;
3983
4286
  return async (payload, req) => {
3984
- const correlationId = payload.correlation_id ?? generateCorrelationId();
4287
+ const correlationId = payload.correlation_id ?? generateCorrelationId2();
3985
4288
  const authResult = await auth(req);
3986
4289
  if (!authResult.authenticated) {
3987
4290
  if (logger) {
@@ -4104,7 +4407,7 @@ function normalizeSignalName(signal) {
4104
4407
  }
4105
4408
  return `SIG${upper}`;
4106
4409
  }
4107
- function generateCorrelationId() {
4410
+ function generateCorrelationId2() {
4108
4411
  return `sig-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
4109
4412
  }
4110
4413
  function createBearerTokenAuth(expectedToken) {
@@ -4571,6 +4874,8 @@ var init_signals = __esm({
4571
4874
  "src/foundry/signals/index.ts"() {
4572
4875
  init_capabilities2();
4573
4876
  init_catalog();
4877
+ init_config_reload_endpoint();
4878
+ init_control_discovery_endpoint();
4574
4879
  init_convenience();
4575
4880
  init_double_tap();
4576
4881
  init_guards();
@@ -4716,7 +5021,9 @@ __export(foundry_exports, {
4716
5021
  clearMimeTypeCache: () => clearMimeTypeCache,
4717
5022
  clearPatternCache: () => clearPatternCache,
4718
5023
  createBearerTokenAuth: () => createBearerTokenAuth,
5024
+ createConfigReloadEndpoint: () => createConfigReloadEndpoint,
4719
5025
  createConfigReloadHandler: () => createConfigReloadHandler,
5026
+ createControlDiscoveryEndpoint: () => createControlDiscoveryEndpoint,
4720
5027
  createDoubleTapTracker: () => createDoubleTapTracker,
4721
5028
  createSignalEndpoint: () => createSignalEndpoint,
4722
5029
  createSignalManager: () => createSignalManager,
@@ -5772,6 +6079,7 @@ var init_cli = __esm({
5772
6079
  // src/schema/index.ts
5773
6080
  var init_schema = __esm({
5774
6081
  "src/schema/index.ts"() {
6082
+ init_ajv_formats();
5775
6083
  init_cli();
5776
6084
  init_errors();
5777
6085
  init_export();
@@ -5783,6 +6091,32 @@ var init_schema = __esm({
5783
6091
  }
5784
6092
  });
5785
6093
 
6094
+ // src/config/env-alias.ts
6095
+ function resolveEnvAliases(env, aliasToCanonical) {
6096
+ const out = { ...env };
6097
+ const applied = [];
6098
+ const conflicts = [];
6099
+ for (const [aliasKey, canonicalKey] of Object.entries(aliasToCanonical)) {
6100
+ const aliasValue = env[aliasKey];
6101
+ if (aliasValue === void 0 || aliasValue === "") continue;
6102
+ const canonicalValue = env[canonicalKey];
6103
+ if (canonicalValue === void 0 || canonicalValue === "") {
6104
+ out[canonicalKey] = aliasValue;
6105
+ applied.push({ aliasKey, canonicalKey });
6106
+ continue;
6107
+ }
6108
+ if (canonicalValue !== aliasValue) {
6109
+ conflicts.push({
6110
+ canonicalKey,
6111
+ aliasKey,
6112
+ canonicalValue,
6113
+ aliasValue
6114
+ });
6115
+ }
6116
+ }
6117
+ return { env: out, applied, conflicts };
6118
+ }
6119
+
5786
6120
  // src/config/errors.ts
5787
6121
  var ConfigPathError = class _ConfigPathError extends Error {
5788
6122
  constructor(message, cause) {
@@ -6134,6 +6468,33 @@ function parseEnvVars(prefix) {
6134
6468
  }
6135
6469
  return config;
6136
6470
  }
6471
+ function parseEnvVarsWithReport(prefix) {
6472
+ const config = {};
6473
+ const consumedKeys = [];
6474
+ const prefixWithSeparator = `${prefix}_`;
6475
+ for (const [key, value] of Object.entries(process.env)) {
6476
+ if (!value) continue;
6477
+ if (key.startsWith(prefixWithSeparator)) {
6478
+ consumedKeys.push(key);
6479
+ const keyWithoutPrefix = key.slice(prefixWithSeparator.length);
6480
+ const parts = keyWithoutPrefix.split("_").filter((p) => p.length > 0);
6481
+ let current = config;
6482
+ for (let i = 0; i < parts.length; i++) {
6483
+ const part = parts[i].toLowerCase();
6484
+ if (i === parts.length - 1) {
6485
+ current[part] = parseEnvValue(value);
6486
+ } else {
6487
+ if (!current[part] || typeof current[part] !== "object") {
6488
+ current[part] = {};
6489
+ }
6490
+ current = current[part];
6491
+ }
6492
+ }
6493
+ }
6494
+ }
6495
+ consumedKeys.sort();
6496
+ return { config, consumedKeys };
6497
+ }
6137
6498
  async function parseConfigFile(path) {
6138
6499
  const content = await readFile(path, "utf-8");
6139
6500
  const ext = extname(path).toLowerCase();
@@ -6167,8 +6528,10 @@ async function loadConfig(options) {
6167
6528
  activeLayers.push("user");
6168
6529
  }
6169
6530
  const envPrefix = options.envPrefix || (identity.app ? identity.app.toUpperCase().replace(/-/g, "_") : "APP");
6170
- const envConfig = parseEnvVars(envPrefix);
6171
- if (Object.keys(envConfig).length > 0) {
6531
+ const includeEnvVarReport = options.includeEnvVarReport === true;
6532
+ const envVars = includeEnvVarReport ? parseEnvVarsWithReport(envPrefix) : null;
6533
+ const envConfig = includeEnvVarReport ? envVars?.config : parseEnvVars(envPrefix);
6534
+ if (envConfig && typeof envConfig === "object" && Object.keys(envConfig).length > 0) {
6172
6535
  mergedConfig = deepMerge(mergedConfig, envConfig);
6173
6536
  activeLayers.push("env");
6174
6537
  }
@@ -6197,6 +6560,8 @@ async function loadConfig(options) {
6197
6560
  defaultsPath,
6198
6561
  userConfigPath,
6199
6562
  envPrefix,
6563
+ envVarsConsumed: includeEnvVarReport ? envVars?.consumedKeys : void 0,
6564
+ envVarsConsumedCount: includeEnvVarReport ? envVars?.consumedKeys.length : void 0,
6200
6565
  activeLayers,
6201
6566
  schema: {
6202
6567
  path: options.schemaPath || null,
@@ -6209,6 +6574,6 @@ async function loadConfig(options) {
6209
6574
  // src/config/index.ts
6210
6575
  var VERSION2 = "0.1.0";
6211
6576
 
6212
- export { ConfigPathError, ConfigValidationError, VERSION2 as VERSION, ensureDirExists, getAppCacheDir, getAppConfigDir, getAppDataDir, getConfigSearchPaths, getFulmenCacheDir, getFulmenConfigDir, getFulmenDataDir, getXDGBaseDirs, loadConfig, resolveConfigPath };
6577
+ export { ConfigPathError, ConfigValidationError, VERSION2 as VERSION, ensureDirExists, getAppCacheDir, getAppConfigDir, getAppDataDir, getConfigSearchPaths, getFulmenCacheDir, getFulmenConfigDir, getFulmenDataDir, getXDGBaseDirs, loadConfig, resolveConfigPath, resolveEnvAliases };
6213
6578
  //# sourceMappingURL=index.js.map
6214
6579
  //# sourceMappingURL=index.js.map