@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,11 +1,14 @@
1
1
  import { readFile, access, mkdir, writeFile, readdir, stat } from 'fs/promises';
2
2
  import { dirname, join, extname, relative } from 'path';
3
3
  import { parse, stringify, parseDocument } from 'yaml';
4
+ import addFormats from 'ajv-formats';
4
5
  import { spawn } from 'child_process';
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';
@@ -727,6 +730,27 @@ var init_types = __esm({
727
730
  "src/telemetry/types.ts"() {
728
731
  }
729
732
  });
733
+ function applyFulmenAjvFormats(ajv, options = {}) {
734
+ const mode = options.mode ?? "fast";
735
+ const formats = options.formats ?? DEFAULT_FORMATS;
736
+ addFormats(ajv, { mode, formats });
737
+ return ajv;
738
+ }
739
+ var DEFAULT_FORMATS;
740
+ var init_ajv_formats = __esm({
741
+ "src/schema/ajv-formats.ts"() {
742
+ DEFAULT_FORMATS = [
743
+ "date-time",
744
+ "email",
745
+ "hostname",
746
+ "ipv4",
747
+ "ipv6",
748
+ "uri",
749
+ "uri-reference",
750
+ "uuid"
751
+ ];
752
+ }
753
+ });
730
754
 
731
755
  // src/schema/errors.ts
732
756
  var errors_exports = {};
@@ -1451,20 +1475,14 @@ async function loadMetaSchema(draft) {
1451
1475
  const content = await readFile(metaSchemaPath, "utf-8");
1452
1476
  return JSON.parse(content);
1453
1477
  }
1454
- async function loadVocabularySchemas() {
1478
+ async function loadVocabularySchemas(draft) {
1479
+ if (draft !== "draft-2019-09" && draft !== "draft-2020-12") {
1480
+ return [];
1481
+ }
1455
1482
  const __filename3 = fileURLToPath(import.meta.url);
1456
1483
  const __dirname4 = dirname(__filename3);
1457
- const vocabDir = join(
1458
- __dirname4,
1459
- "..",
1460
- "..",
1461
- "schemas",
1462
- "crucible-ts",
1463
- "meta",
1464
- "draft-2020-12",
1465
- "meta"
1466
- );
1467
- const vocabFiles = [
1484
+ const vocabDir = join(__dirname4, "..", "..", "schemas", "crucible-ts", "meta", draft, "meta");
1485
+ const vocabFiles = draft === "draft-2020-12" ? [
1468
1486
  "core.json",
1469
1487
  "applicator.json",
1470
1488
  "unevaluated.json",
@@ -1472,6 +1490,13 @@ async function loadVocabularySchemas() {
1472
1490
  "meta-data.json",
1473
1491
  "format-annotation.json",
1474
1492
  "content.json"
1493
+ ] : [
1494
+ "core.json",
1495
+ "applicator.json",
1496
+ "validation.json",
1497
+ "meta-data.json",
1498
+ "format.json",
1499
+ "content.json"
1475
1500
  ];
1476
1501
  const schemas = [];
1477
1502
  for (const file of vocabFiles) {
@@ -1526,47 +1551,65 @@ async function loadReferencedSchema(uri) {
1526
1551
  }
1527
1552
  return JSON.parse(content);
1528
1553
  }
1529
- function getAjv() {
1530
- if (!ajvInstance) {
1531
- ajvInstance = new Ajv({
1532
- strict: false,
1533
- allErrors: true,
1534
- verbose: true,
1535
- // Allow schemas with $id to be added without replacing existing ones
1536
- addUsedSchema: false,
1537
- // Enable async schema loading for YAML references
1538
- loadSchema: loadReferencedSchema
1539
- });
1540
- addFormats(ajvInstance, {
1541
- mode: "fast",
1542
- formats: ["date-time", "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference"]
1543
- });
1544
- metaschemaReady = Promise.all([loadVocabularySchemas(), loadMetaSchema("draft-2020-12")]).then(([vocabSchemas, metaSchema]) => {
1545
- if (ajvInstance) {
1546
- for (const vocabSchema of vocabSchemas) {
1547
- try {
1548
- ajvInstance.addMetaSchema(vocabSchema);
1549
- } catch {
1550
- }
1551
- }
1552
- ajvInstance.addMetaSchema(metaSchema);
1553
- }
1554
- }).catch((error) => {
1555
- throw new Error(`Failed to load metaschemas: ${error}`);
1556
- });
1554
+ function detectDialect(schema) {
1555
+ if (schema && typeof schema === "object" && !Array.isArray(schema)) {
1556
+ const maybeSchema = schema;
1557
+ const declared = maybeSchema.$schema;
1558
+ if (typeof declared === "string") {
1559
+ if (declared.includes("draft-04")) return "draft-04";
1560
+ if (declared.includes("draft-06")) return "draft-06";
1561
+ if (declared.includes("draft-07")) return "draft-07";
1562
+ if (declared.includes("draft/2019-09")) return "draft-2019-09";
1563
+ if (declared.includes("draft/2020-12")) return "draft-2020-12";
1564
+ }
1557
1565
  }
1558
- return ajvInstance;
1566
+ return "draft-2020-12";
1567
+ }
1568
+ function createAjv(dialect) {
1569
+ const AjvCtor = dialect === "draft-2020-12" ? Ajv2020 : dialect === "draft-2019-09" ? Ajv2019 : dialect === "draft-04" ? AjvDraft04 : Ajv;
1570
+ const ajv = new AjvCtor({
1571
+ strict: false,
1572
+ allErrors: true,
1573
+ verbose: true,
1574
+ // Allow schemas with $id to be added without replacing existing ones
1575
+ addUsedSchema: false,
1576
+ // draft-04 uses "id"; later drafts use "$id"
1577
+ schemaId: dialect === "draft-04" ? "id" : "$id",
1578
+ // Enable async schema loading for YAML references
1579
+ loadSchema: loadReferencedSchema
1580
+ });
1581
+ applyFulmenAjvFormats(ajv);
1582
+ return ajv;
1583
+ }
1584
+ async function getAjv(dialect) {
1585
+ const existing = ajvInstances.get(dialect);
1586
+ if (existing) {
1587
+ const ready = metaschemaReady.get(dialect);
1588
+ if (ready) await ready;
1589
+ return existing;
1590
+ }
1591
+ const ajv = createAjv(dialect);
1592
+ ajvInstances.set(dialect, ajv);
1593
+ const readyPromise = Promise.all([loadVocabularySchemas(dialect), loadMetaSchema(dialect)]).then(([vocabSchemas, metaSchema]) => {
1594
+ for (const vocabSchema of vocabSchemas) {
1595
+ try {
1596
+ ajv.addMetaSchema(vocabSchema);
1597
+ } catch {
1598
+ }
1599
+ }
1600
+ try {
1601
+ ajv.addMetaSchema(metaSchema);
1602
+ } catch {
1603
+ }
1604
+ }).catch((error) => {
1605
+ throw new Error(`Failed to load metaschemas (${dialect}): ${error}`);
1606
+ });
1607
+ metaschemaReady.set(dialect, readyPromise);
1608
+ await readyPromise;
1609
+ return ajv;
1559
1610
  }
1560
1611
  async function compileSchema(schema, options = {}) {
1561
- const ajv = getAjv();
1562
- if (metaschemaReady) {
1563
- await metaschemaReady;
1564
- }
1565
- const cacheKey = typeof schema === "string" ? schema : JSON.stringify(schema);
1566
- const cached = schemaCache.get(cacheKey);
1567
- if (cached !== void 0) {
1568
- return cached;
1569
- }
1612
+ const baseKey = typeof schema === "string" ? schema : JSON.stringify(schema);
1570
1613
  let parsedSchema;
1571
1614
  if (typeof schema === "string") {
1572
1615
  try {
@@ -1584,18 +1627,27 @@ async function compileSchema(schema, options = {}) {
1584
1627
  } else {
1585
1628
  parsedSchema = schema;
1586
1629
  }
1630
+ const dialect = detectDialect(parsedSchema);
1631
+ const ajv = await getAjv(dialect);
1632
+ const cacheKey = `${dialect}:${baseKey}`;
1633
+ const cached = schemaCache.get(cacheKey);
1634
+ if (cached !== void 0) {
1635
+ return cached;
1636
+ }
1587
1637
  try {
1588
1638
  if (options.aliases && options.aliases.length > 0) {
1589
1639
  for (const alias of options.aliases) {
1590
1640
  if (alias && ajv.getSchema(alias) === void 0) {
1591
1641
  try {
1592
- ajv.addSchema(parsedSchema, alias);
1642
+ if (typeof parsedSchema === "object" && parsedSchema !== null) {
1643
+ ajv.addSchema(parsedSchema, alias);
1644
+ }
1593
1645
  } catch {
1594
1646
  }
1595
1647
  }
1596
1648
  }
1597
1649
  }
1598
- const validator = await ajv.compileAsync(parsedSchema);
1650
+ const validator = typeof parsedSchema === "boolean" ? ajv.compile(parsedSchema) : await ajv.compileAsync(parsedSchema);
1599
1651
  schemaCache.set(cacheKey, validator);
1600
1652
  return validator;
1601
1653
  } catch (error) {
@@ -1665,8 +1717,39 @@ async function validateFile(filePath, validator) {
1665
1717
  }
1666
1718
  async function validateSchema(schema) {
1667
1719
  try {
1668
- const validator = await compileSchema(schema);
1669
- validateData({}, validator);
1720
+ let parsedSchema;
1721
+ if (typeof schema === "string") {
1722
+ try {
1723
+ parsedSchema = JSON.parse(schema);
1724
+ } catch {
1725
+ parsedSchema = parse(schema);
1726
+ }
1727
+ } else if (Buffer.isBuffer(schema)) {
1728
+ const content = schema.toString("utf-8");
1729
+ try {
1730
+ parsedSchema = JSON.parse(content);
1731
+ } catch {
1732
+ parsedSchema = parse(content);
1733
+ }
1734
+ } else {
1735
+ parsedSchema = schema;
1736
+ }
1737
+ const dialect = detectDialect(parsedSchema);
1738
+ const ajv = await getAjv(dialect);
1739
+ const metaValid = ajv.validateSchema(parsedSchema);
1740
+ if (!metaValid && ajv.errors) {
1741
+ const diagnostics = ajv.errors.map(
1742
+ (error) => createDiagnostic(
1743
+ error.instancePath || "",
1744
+ error.message || "Schema meta-validation failed",
1745
+ error.keyword || "unknown",
1746
+ "ERROR",
1747
+ "ajv"
1748
+ )
1749
+ );
1750
+ return { valid: false, diagnostics, source: "ajv" };
1751
+ }
1752
+ await compileSchema(parsedSchema);
1670
1753
  return {
1671
1754
  valid: true,
1672
1755
  diagnostics: [],
@@ -1737,14 +1820,16 @@ async function validateFileBySchemaId(filePath, schemaId, registryOptions) {
1737
1820
  throw error;
1738
1821
  }
1739
1822
  }
1740
- var ajvInstance, metaschemaReady, schemaCache;
1823
+ var ajvInstances, metaschemaReady, schemaCache;
1741
1824
  var init_validator = __esm({
1742
1825
  "src/schema/validator.ts"() {
1743
1826
  init_telemetry();
1827
+ init_ajv_formats();
1744
1828
  init_errors();
1745
1829
  init_registry2();
1746
1830
  init_utils();
1747
- metaschemaReady = null;
1831
+ ajvInstances = /* @__PURE__ */ new Map();
1832
+ metaschemaReady = /* @__PURE__ */ new Map();
1748
1833
  schemaCache = /* @__PURE__ */ new Map();
1749
1834
  }
1750
1835
  });
@@ -3699,6 +3784,224 @@ var init_capabilities2 = __esm({
3699
3784
  }
3700
3785
  });
3701
3786
 
3787
+ // src/foundry/signals/config-reload-endpoint.ts
3788
+ function createConfigReloadEndpoint(options) {
3789
+ const { loader, validator, onReload: onReload2, auth, rateLimit, logger, telemetry } = options;
3790
+ return async (payload, req) => {
3791
+ const correlationId = payload.correlation_id ?? generateCorrelationId();
3792
+ const authResult = await auth(req);
3793
+ if (!authResult.authenticated) {
3794
+ if (logger) {
3795
+ logger.warn("Config reload endpoint: authentication failed", {
3796
+ correlation_id: correlationId,
3797
+ reason: authResult.reason
3798
+ });
3799
+ }
3800
+ if (telemetry) {
3801
+ telemetry.emit("fulmen.config.http_endpoint.auth_failed", {
3802
+ correlation_id: correlationId
3803
+ });
3804
+ }
3805
+ return {
3806
+ status: "error",
3807
+ error: "authentication_failed",
3808
+ message: authResult.reason || "Authentication required",
3809
+ statusCode: 401
3810
+ };
3811
+ }
3812
+ const identity = authResult.identity || "unknown";
3813
+ if (rateLimit) {
3814
+ const rateLimitResult = await rateLimit(identity);
3815
+ if (!rateLimitResult.allowed) {
3816
+ if (logger) {
3817
+ logger.warn("Config reload endpoint: rate limit exceeded", {
3818
+ correlation_id: correlationId,
3819
+ identity
3820
+ });
3821
+ }
3822
+ if (telemetry) {
3823
+ telemetry.emit("fulmen.config.http_endpoint.rate_limited", {
3824
+ correlation_id: correlationId
3825
+ });
3826
+ }
3827
+ return {
3828
+ status: "error",
3829
+ error: "rate_limit_exceeded",
3830
+ message: "Rate limit exceeded. Please try again later.",
3831
+ statusCode: 429
3832
+ };
3833
+ }
3834
+ }
3835
+ if (telemetry) {
3836
+ telemetry.emit("fulmen.config.http_endpoint.reload_requested", {
3837
+ correlation_id: correlationId
3838
+ });
3839
+ }
3840
+ try {
3841
+ const config = await loader();
3842
+ if (validator) {
3843
+ const validation = await validator(config);
3844
+ if (!validation.valid) {
3845
+ if (logger) {
3846
+ logger.warn("Config reload endpoint: validation failed", {
3847
+ correlation_id: correlationId,
3848
+ error_count: validation.errors?.length ?? 0
3849
+ });
3850
+ }
3851
+ if (telemetry) {
3852
+ telemetry.emit("fulmen.config.http_endpoint.reload_rejected", {
3853
+ correlation_id: correlationId,
3854
+ reason: "validation_failed"
3855
+ });
3856
+ }
3857
+ return {
3858
+ status: "error",
3859
+ error: "validation_failed",
3860
+ message: "Configuration validation failed",
3861
+ validation_errors: validation.errors,
3862
+ statusCode: 422
3863
+ };
3864
+ }
3865
+ }
3866
+ if (onReload2) {
3867
+ await onReload2(config);
3868
+ }
3869
+ if (telemetry) {
3870
+ telemetry.emit("fulmen.config.http_endpoint.reload_accepted", {
3871
+ correlation_id: correlationId
3872
+ });
3873
+ }
3874
+ if (logger) {
3875
+ logger.info("Config reload endpoint: reload accepted", {
3876
+ correlation_id: correlationId,
3877
+ reason: payload.reason
3878
+ });
3879
+ }
3880
+ return {
3881
+ status: "reloaded",
3882
+ correlation_id: correlationId,
3883
+ message: "Configuration reloaded",
3884
+ statusCode: 200
3885
+ };
3886
+ } catch (error) {
3887
+ if (logger) {
3888
+ logger.warn("Config reload endpoint: reload failed", {
3889
+ correlation_id: correlationId,
3890
+ error: error instanceof Error ? error.message : String(error)
3891
+ });
3892
+ }
3893
+ if (telemetry) {
3894
+ telemetry.emit("fulmen.config.http_endpoint.reload_error", {
3895
+ correlation_id: correlationId,
3896
+ error_type: error instanceof Error ? error.constructor.name : "unknown"
3897
+ });
3898
+ }
3899
+ return {
3900
+ status: "error",
3901
+ error: "reload_failed",
3902
+ message: error instanceof Error ? error.message : String(error),
3903
+ statusCode: 500
3904
+ };
3905
+ }
3906
+ };
3907
+ }
3908
+ function generateCorrelationId() {
3909
+ return `cfg-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
3910
+ }
3911
+ var init_config_reload_endpoint = __esm({
3912
+ "src/foundry/signals/config-reload-endpoint.ts"() {
3913
+ }
3914
+ });
3915
+
3916
+ // src/appidentity/runtime.ts
3917
+ function detectRuntime() {
3918
+ const versions = process.versions;
3919
+ if (typeof versions.bun === "string" && versions.bun.length > 0) {
3920
+ return { name: "bun", version: versions.bun };
3921
+ }
3922
+ if (typeof versions.node === "string" && versions.node.length > 0) {
3923
+ return { name: "node", version: versions.node };
3924
+ }
3925
+ return { name: "unknown" };
3926
+ }
3927
+ function buildRuntimeInfo(options = {}) {
3928
+ const runtime = detectRuntime();
3929
+ const serviceName = options.serviceName ?? options.identity?.app.binary_name ?? "unknown-service";
3930
+ const vendor = options.vendor ?? options.identity?.app.vendor;
3931
+ return {
3932
+ service: {
3933
+ name: serviceName,
3934
+ vendor,
3935
+ version: options.version
3936
+ },
3937
+ runtime,
3938
+ platform: {
3939
+ os: process.platform,
3940
+ arch: process.arch
3941
+ }
3942
+ };
3943
+ }
3944
+ var init_runtime = __esm({
3945
+ "src/appidentity/runtime.ts"() {
3946
+ }
3947
+ });
3948
+
3949
+ // src/foundry/signals/control-discovery-endpoint.ts
3950
+ function createControlDiscoveryEndpoint(options) {
3951
+ const { identity, version, endpoints, auth, authSummary, logger, telemetry } = options;
3952
+ return async (req) => {
3953
+ if (auth) {
3954
+ const authResult = await auth(req);
3955
+ if (!authResult.authenticated) {
3956
+ if (logger) {
3957
+ logger.warn("Control discovery endpoint: authentication failed", {
3958
+ reason: authResult.reason
3959
+ });
3960
+ }
3961
+ if (telemetry) {
3962
+ telemetry.emit("fulmen.control.discovery.auth_failed", {
3963
+ service: identity.app.binary_name
3964
+ });
3965
+ }
3966
+ return {
3967
+ status: "error",
3968
+ error: "authentication_failed",
3969
+ message: authResult.reason || "Authentication required",
3970
+ statusCode: 401
3971
+ };
3972
+ }
3973
+ }
3974
+ if (telemetry) {
3975
+ telemetry.emit("fulmen.control.discovery.served", {
3976
+ service: identity.app.binary_name
3977
+ });
3978
+ }
3979
+ const runtime = buildRuntimeInfo({ identity, version });
3980
+ return {
3981
+ status: "ok",
3982
+ service: {
3983
+ name: identity.app.binary_name,
3984
+ vendor: identity.app.vendor,
3985
+ version
3986
+ },
3987
+ runtime: {
3988
+ name: runtime.runtime.name,
3989
+ version: runtime.runtime.version,
3990
+ platform: runtime.platform.os,
3991
+ arch: runtime.platform.arch
3992
+ },
3993
+ auth_summary: authSummary,
3994
+ endpoints,
3995
+ statusCode: 200
3996
+ };
3997
+ };
3998
+ }
3999
+ var init_control_discovery_endpoint = __esm({
4000
+ "src/foundry/signals/control-discovery-endpoint.ts"() {
4001
+ init_runtime();
4002
+ }
4003
+ });
4004
+
3702
4005
  // src/foundry/signals/convenience.ts
3703
4006
  async function onShutdown(manager, handler, options = {}) {
3704
4007
  await manager.register("SIGTERM", handler, options);
@@ -3958,7 +4261,7 @@ var init_guards = __esm({
3958
4261
  function createSignalEndpoint(options) {
3959
4262
  const { manager, auth, rateLimit, logger, telemetry, allowedSignals } = options;
3960
4263
  return async (payload, req) => {
3961
- const correlationId = payload.correlation_id ?? generateCorrelationId();
4264
+ const correlationId = payload.correlation_id ?? generateCorrelationId2();
3962
4265
  const authResult = await auth(req);
3963
4266
  if (!authResult.authenticated) {
3964
4267
  if (logger) {
@@ -4081,7 +4384,7 @@ function normalizeSignalName(signal) {
4081
4384
  }
4082
4385
  return `SIG${upper}`;
4083
4386
  }
4084
- function generateCorrelationId() {
4387
+ function generateCorrelationId2() {
4085
4388
  return `sig-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
4086
4389
  }
4087
4390
  function createBearerTokenAuth(expectedToken) {
@@ -4548,6 +4851,8 @@ var init_signals = __esm({
4548
4851
  "src/foundry/signals/index.ts"() {
4549
4852
  init_capabilities2();
4550
4853
  init_catalog();
4854
+ init_config_reload_endpoint();
4855
+ init_control_discovery_endpoint();
4551
4856
  init_convenience();
4552
4857
  init_double_tap();
4553
4858
  init_guards();
@@ -4693,7 +4998,9 @@ __export(foundry_exports, {
4693
4998
  clearMimeTypeCache: () => clearMimeTypeCache,
4694
4999
  clearPatternCache: () => clearPatternCache,
4695
5000
  createBearerTokenAuth: () => createBearerTokenAuth,
5001
+ createConfigReloadEndpoint: () => createConfigReloadEndpoint,
4696
5002
  createConfigReloadHandler: () => createConfigReloadHandler,
5003
+ createControlDiscoveryEndpoint: () => createControlDiscoveryEndpoint,
4697
5004
  createDoubleTapTracker: () => createDoubleTapTracker,
4698
5005
  createSignalEndpoint: () => createSignalEndpoint,
4699
5006
  createSignalManager: () => createSignalManager,
@@ -5749,6 +6056,7 @@ var init_cli = __esm({
5749
6056
  // src/schema/index.ts
5750
6057
  var init_schema = __esm({
5751
6058
  "src/schema/index.ts"() {
6059
+ init_ajv_formats();
5752
6060
  init_cli();
5753
6061
  init_errors();
5754
6062
  init_export();