monora-ai 2.0.0 → 2.1.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 (202) hide show
  1. package/README.md +441 -150
  2. package/dist/aims_governance.d.ts +238 -0
  3. package/dist/aims_governance.d.ts.map +1 -0
  4. package/dist/aims_governance.js +922 -0
  5. package/dist/alerts.d.ts +16 -0
  6. package/dist/alerts.d.ts.map +1 -1
  7. package/dist/alerts.js +16 -0
  8. package/dist/api.d.ts +6 -0
  9. package/dist/api.d.ts.map +1 -1
  10. package/dist/api.js +6 -0
  11. package/dist/assessment.d.ts +269 -0
  12. package/dist/assessment.d.ts.map +1 -0
  13. package/dist/assessment.js +1232 -0
  14. package/dist/attestation.js +23 -1
  15. package/dist/attribution.d.ts +349 -0
  16. package/dist/attribution.d.ts.map +1 -0
  17. package/dist/attribution.js +987 -0
  18. package/dist/autodetect.d.ts +69 -1
  19. package/dist/autodetect.d.ts.map +1 -1
  20. package/dist/autodetect.js +644 -1
  21. package/dist/bias.d.ts +130 -0
  22. package/dist/bias.d.ts.map +1 -0
  23. package/dist/bias.js +223 -0
  24. package/dist/circuit_breaker.js +3 -3
  25. package/dist/cli/diagnostics.d.ts +5 -1
  26. package/dist/cli/diagnostics.d.ts.map +1 -1
  27. package/dist/cli/diagnostics.js +31 -8
  28. package/dist/cli/doctor.d.ts +25 -0
  29. package/dist/cli/doctor.d.ts.map +1 -0
  30. package/dist/cli/doctor.js +381 -0
  31. package/dist/cli/fix.d.ts +16 -0
  32. package/dist/cli/fix.d.ts.map +1 -0
  33. package/dist/cli/fix.js +284 -0
  34. package/dist/cli/init.d.ts +57 -0
  35. package/dist/cli/init.d.ts.map +1 -0
  36. package/dist/cli/init.js +205 -0
  37. package/dist/cli.js +1611 -126
  38. package/dist/complianceTargets.d.ts +111 -0
  39. package/dist/complianceTargets.d.ts.map +1 -0
  40. package/dist/complianceTargets.js +521 -0
  41. package/dist/config.d.ts +301 -17
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +428 -36
  44. package/dist/config_migrations.d.ts +41 -0
  45. package/dist/config_migrations.d.ts.map +1 -1
  46. package/dist/config_migrations.js +205 -0
  47. package/dist/config_schema.d.ts +2900 -731
  48. package/dist/config_schema.d.ts.map +1 -1
  49. package/dist/config_schema.js +257 -55
  50. package/dist/context.d.ts +34 -0
  51. package/dist/context.d.ts.map +1 -1
  52. package/dist/context.js +118 -7
  53. package/dist/control_backbone.d.ts +122 -0
  54. package/dist/control_backbone.d.ts.map +1 -0
  55. package/dist/control_backbone.js +698 -0
  56. package/dist/data-governance.d.ts +187 -0
  57. package/dist/data-governance.d.ts.map +1 -0
  58. package/dist/data-governance.js +424 -0
  59. package/dist/dataResidency.d.ts +44 -0
  60. package/dist/dataResidency.d.ts.map +1 -0
  61. package/dist/dataResidency.js +203 -0
  62. package/dist/dispatcher.d.ts +32 -0
  63. package/dist/dispatcher.d.ts.map +1 -1
  64. package/dist/dispatcher.js +91 -4
  65. package/dist/events.d.ts.map +1 -1
  66. package/dist/events.js +38 -0
  67. package/dist/evidence_store.d.ts +103 -0
  68. package/dist/evidence_store.d.ts.map +1 -0
  69. package/dist/evidence_store.js +459 -0
  70. package/dist/executiveSummary.d.ts +65 -8
  71. package/dist/executiveSummary.d.ts.map +1 -1
  72. package/dist/executiveSummary.js +289 -26
  73. package/dist/identity.d.ts +143 -0
  74. package/dist/identity.d.ts.map +1 -0
  75. package/dist/identity.js +231 -0
  76. package/dist/impact-assessment.d.ts +350 -0
  77. package/dist/impact-assessment.d.ts.map +1 -0
  78. package/dist/impact-assessment.js +580 -0
  79. package/dist/index.d.ts +25 -5
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +300 -4
  82. package/dist/instrumentation.d.ts +1 -1
  83. package/dist/instrumentation.d.ts.map +1 -1
  84. package/dist/instrumentation.js +243 -27
  85. package/dist/integrations/anthropic.d.ts +3 -0
  86. package/dist/integrations/anthropic.d.ts.map +1 -1
  87. package/dist/integrations/anthropic.js +284 -79
  88. package/dist/integrations/governance.d.ts +33 -0
  89. package/dist/integrations/governance.d.ts.map +1 -0
  90. package/dist/integrations/governance.js +208 -0
  91. package/dist/integrations/langchain.d.ts +7 -0
  92. package/dist/integrations/langchain.d.ts.map +1 -1
  93. package/dist/integrations/langchain.js +387 -143
  94. package/dist/integrations/openai.d.ts +9 -0
  95. package/dist/integrations/openai.d.ts.map +1 -1
  96. package/dist/integrations/openai.js +673 -73
  97. package/dist/iso42001_consolidation.d.ts +16 -0
  98. package/dist/iso42001_consolidation.d.ts.map +1 -0
  99. package/dist/iso42001_consolidation.js +413 -0
  100. package/dist/iso42001_workflows.d.ts +263 -0
  101. package/dist/iso42001_workflows.d.ts.map +1 -0
  102. package/dist/iso42001_workflows.js +781 -0
  103. package/dist/lifecycle.d.ts +299 -0
  104. package/dist/lifecycle.d.ts.map +1 -0
  105. package/dist/lifecycle.js +624 -0
  106. package/dist/lineage.d.ts +2 -2
  107. package/dist/lineage.d.ts.map +1 -1
  108. package/dist/lineage.js +12 -17
  109. package/dist/middleware/express.d.ts.map +1 -1
  110. package/dist/middleware/express.js +33 -3
  111. package/dist/middleware/nextjs.d.ts.map +1 -1
  112. package/dist/middleware/nextjs.js +42 -68
  113. package/dist/model.d.ts +143 -0
  114. package/dist/model.d.ts.map +1 -0
  115. package/dist/model.js +371 -0
  116. package/dist/onboarding.d.ts +42 -0
  117. package/dist/onboarding.d.ts.map +1 -0
  118. package/dist/onboarding.js +1022 -0
  119. package/dist/oversight.d.ts +264 -0
  120. package/dist/oversight.d.ts.map +1 -0
  121. package/dist/oversight.js +497 -0
  122. package/dist/pdf_report.d.ts.map +1 -1
  123. package/dist/pdf_report.js +42 -21
  124. package/dist/presets.d.ts +88 -0
  125. package/dist/presets.d.ts.map +1 -0
  126. package/dist/presets.js +520 -0
  127. package/dist/propagation.d.ts.map +1 -1
  128. package/dist/propagation.js +34 -2
  129. package/dist/quotas.d.ts +171 -0
  130. package/dist/quotas.d.ts.map +1 -0
  131. package/dist/quotas.js +259 -0
  132. package/dist/register.d.ts +13 -0
  133. package/dist/register.d.ts.map +1 -0
  134. package/dist/register.js +99 -0
  135. package/dist/registry.d.ts +1 -0
  136. package/dist/registry.d.ts.map +1 -1
  137. package/dist/registry.js +7 -0
  138. package/dist/registryData.json +43 -6
  139. package/dist/report.d.ts +2 -1
  140. package/dist/report.d.ts.map +1 -1
  141. package/dist/report.js +189 -2
  142. package/dist/reporting.d.ts +125 -0
  143. package/dist/reporting.d.ts.map +1 -1
  144. package/dist/reporting.js +196 -5
  145. package/dist/resources.d.ts +285 -0
  146. package/dist/resources.d.ts.map +1 -0
  147. package/dist/resources.js +643 -0
  148. package/dist/risk.d.ts +120 -0
  149. package/dist/risk.d.ts.map +1 -0
  150. package/dist/risk.js +220 -0
  151. package/dist/runtime.d.ts +74 -1
  152. package/dist/runtime.d.ts.map +1 -1
  153. package/dist/runtime.js +598 -22
  154. package/dist/schemaInference.d.ts +92 -0
  155. package/dist/schemaInference.d.ts.map +1 -0
  156. package/dist/schemaInference.js +466 -0
  157. package/dist/schema_validation.js +2 -2
  158. package/dist/schemas/config.schema.json +169 -6
  159. package/dist/schemas/event.schema.json +4 -0
  160. package/dist/security_report.js +4 -4
  161. package/dist/signing.d.ts +1 -1
  162. package/dist/signing.d.ts.map +1 -1
  163. package/dist/signing.js +4 -0
  164. package/dist/sinks/file.d.ts +19 -1
  165. package/dist/sinks/file.d.ts.map +1 -1
  166. package/dist/sinks/file.js +82 -13
  167. package/dist/sinks/https.d.ts +10 -0
  168. package/dist/sinks/https.d.ts.map +1 -1
  169. package/dist/sinks/https.js +76 -16
  170. package/dist/sinks/stdout.d.ts +1 -0
  171. package/dist/sinks/stdout.d.ts.map +1 -1
  172. package/dist/sinks/stdout.js +12 -1
  173. package/dist/spec.d.ts +159 -0
  174. package/dist/spec.d.ts.map +1 -0
  175. package/dist/spec.js +391 -0
  176. package/dist/stakeholders.d.ts +199 -0
  177. package/dist/stakeholders.d.ts.map +1 -0
  178. package/dist/stakeholders.js +398 -0
  179. package/dist/standards.d.ts.map +1 -1
  180. package/dist/standards.js +160 -2
  181. package/dist/standards_ingest.d.ts +2 -2
  182. package/dist/standards_ingest.d.ts.map +1 -1
  183. package/dist/standards_ingest.js +105 -23
  184. package/dist/streaming.d.ts.map +1 -1
  185. package/dist/streaming.js +7 -2
  186. package/dist/telemetry.d.ts +16 -2
  187. package/dist/telemetry.d.ts.map +1 -1
  188. package/dist/telemetry.js +79 -14
  189. package/dist/templates/controls/iso42001_control_catalog.json +1443 -0
  190. package/dist/traced_emitter.d.ts +3 -0
  191. package/dist/traced_emitter.d.ts.map +1 -1
  192. package/dist/traced_emitter.js +142 -25
  193. package/dist/trust_package.d.ts +21 -1
  194. package/dist/trust_package.d.ts.map +1 -1
  195. package/dist/trust_package.js +101 -4
  196. package/dist/verify.d.ts.map +1 -1
  197. package/dist/verify.js +9 -2
  198. package/dist/wal.d.ts.map +1 -1
  199. package/dist/wal.js +2 -1
  200. package/package.json +14 -1
  201. package/scripts/postinstall.js +119 -97
  202. package/templates/controls/iso42001_control_catalog.json +1443 -0
package/dist/runtime.js CHANGED
@@ -12,11 +12,17 @@ exports.llmCall = llmCall;
12
12
  exports.toolCall = toolCall;
13
13
  exports.agentStep = agentStep;
14
14
  exports.ensureState = ensureState;
15
+ exports.getState = getState;
15
16
  exports.emitEvent = emitEvent;
17
+ exports.emitInternal = emitInternal;
18
+ exports.notifyViolation = notifyViolation;
16
19
  exports.executeCall = executeCall;
20
+ exports.applyQuotaLimits = applyQuotaLimits;
17
21
  exports.buildLlmBody = buildLlmBody;
18
22
  exports.buildToolBody = buildToolBody;
19
23
  exports.buildAgentBody = buildAgentBody;
24
+ exports.extractUsageMetrics = extractUsageMetrics;
25
+ exports.estimateUsageCost = estimateUsageCost;
20
26
  exports.isPromise = isPromise;
21
27
  const perf_hooks_1 = require("perf_hooks");
22
28
  const logger_1 = require("./logger");
@@ -36,9 +42,29 @@ const report_1 = require("./report");
36
42
  const wal_1 = require("./wal");
37
43
  const signing_1 = require("./signing");
38
44
  const instrumentation_1 = require("./instrumentation");
39
- const streaming_1 = require("./streaming");
40
45
  const telemetry_1 = require("./telemetry");
46
+ const attribution_1 = require("./attribution");
47
+ const config_migrations_1 = require("./config_migrations");
48
+ const quotas_1 = require("./quotas");
41
49
  let state = null;
50
+ let cachedVerifyEventHash = null;
51
+ let verifyEventHashResolved = false;
52
+ function getVerifyEventHash() {
53
+ if (verifyEventHashResolved) {
54
+ return cachedVerifyEventHash;
55
+ }
56
+ verifyEventHashResolved = true;
57
+ try {
58
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
59
+ const verifyModule = require('./verify');
60
+ cachedVerifyEventHash = verifyModule.verifyEventHash;
61
+ }
62
+ catch (error) {
63
+ logger_1.logger.error('Real-time verification unavailable: %s', error);
64
+ cachedVerifyEventHash = null;
65
+ }
66
+ return cachedVerifyEventHash;
67
+ }
42
68
  /**
43
69
  * Initialize Monora with simplified configuration.
44
70
  *
@@ -55,14 +81,25 @@ let state = null;
55
81
  * init({ configPath: 'monora.yml' }); // Load from config file
56
82
  * init({ configDict: {...} }); // Provide full config dict
57
83
  * ```
84
+ *
85
+ * @remarks
86
+ * Cross-SDK Parity: This function is async (returns Promise) in Node.js but
87
+ * synchronous in Python. This is an intentional language difference:
88
+ * - Node.js: `await monora.init({ preset: 'production' })`
89
+ * - Python: `monora.init("production")`
58
90
  */
59
91
  async function init(options) {
60
- const { preset, serviceName, sink, policies, configPath, configDict, envPrefix = 'MONORA_', failFast = false, } = options || {};
92
+ const { preset, serviceName, sink, policies, configPath, configDict, envPrefix = 'MONORA_', failFast = false, onEvent: eventCallback, promptAttribution = false, } = options || {};
61
93
  if (state) {
62
94
  if (state.traceCompletionUnregister) {
63
95
  state.traceCompletionUnregister();
64
96
  }
97
+ if (state.traceStartUnregister) {
98
+ state.traceStartUnregister();
99
+ }
65
100
  state.dispatcher.close();
101
+ // Clear any previously registered callbacks to avoid duplicates on re-init
102
+ (0, dispatcher_1.clearAuditCallbacks)();
66
103
  state = null;
67
104
  }
68
105
  // Auto-detect environment and service info
@@ -106,12 +143,71 @@ async function init(options) {
106
143
  logger_1.logger.info('Auto-detected SDKs: %s', installedSdks.join(', '));
107
144
  }
108
145
  }
109
- await initWithConfig(config, failFast);
146
+ // Check for production readiness warnings
147
+ checkProductionReadiness(config, effectivePreset);
148
+ enforceOnboardingGate(config);
149
+ // Register user-provided event callback before init
150
+ if (eventCallback) {
151
+ (0, dispatcher_1.onEvent)(eventCallback);
152
+ }
153
+ await initWithConfig(config, failFast, { promptAttribution });
154
+ // Send startup telemetry event (opt-in only)
155
+ if ((0, attribution_1.isTelemetryEnabled)()) {
156
+ (0, attribution_1.sendTelemetryEvent)({
157
+ eventType: 'startup',
158
+ environment: detectedEnv,
159
+ eventData: {
160
+ preset: effectivePreset,
161
+ service_name: config.defaults?.service_name,
162
+ },
163
+ });
164
+ }
165
+ }
166
+ /**
167
+ * Check configuration for production readiness and log warnings.
168
+ *
169
+ * This runs automatically when environment is 'production' to warn about
170
+ * settings that may not be appropriate for production use.
171
+ */
172
+ function checkProductionReadiness(config, presetName) {
173
+ const environment = config.defaults?.environment || 'development';
174
+ // Check preset-environment mismatch
175
+ const mismatch = (0, config_migrations_1.checkPresetEnvironmentMismatch)(presetName, environment);
176
+ if (mismatch) {
177
+ (0, config_migrations_1.logProductionWarnings)([mismatch]);
178
+ }
179
+ // Check production readiness (only runs if environment is production)
180
+ const warnings = (0, config_migrations_1.validateProductionReadiness)(config);
181
+ if (warnings.length > 0) {
182
+ (0, config_migrations_1.logProductionWarnings)(warnings);
183
+ }
184
+ }
185
+ function enforceOnboardingGate(config) {
186
+ const environment = String(config.defaults?.environment || '').toLowerCase();
187
+ if (environment !== 'production') {
188
+ return;
189
+ }
190
+ const onboarding = config.onboarding;
191
+ if (!onboarding || typeof onboarding !== 'object') {
192
+ return;
193
+ }
194
+ if (!onboarding.enabled) {
195
+ return;
196
+ }
197
+ if (onboarding.required_in_production === false) {
198
+ return;
199
+ }
200
+ const status = String(onboarding.status || 'draft').toLowerCase();
201
+ if (status === 'completed') {
202
+ return;
203
+ }
204
+ throw new Error('Onboarding is required for production but is not completed. ' +
205
+ 'Run `npx monora-ai onboard validate` and `npx monora-ai onboard complete` first.');
110
206
  }
111
207
  /**
112
208
  * Initialize Monora with a pre-built configuration.
113
209
  */
114
- async function initWithConfig(config, failFast) {
210
+ async function initWithConfig(config, failFast, options) {
115
211
  const sinks = buildSinks(config.sinks || [], failFast);
116
212
  const dispatcher = new dispatcher_1.EventDispatcher(sinks, config);
117
213
  const registry = new registry_1.ModelRegistry(config.registry || {});
@@ -131,7 +227,9 @@ async function initWithConfig(config, failFast) {
131
227
  reportManager,
132
228
  wal,
133
229
  signer,
230
+ quotaManager: new quotas_1.QuotaManager(config.quotas || {}),
134
231
  };
232
+ state.emitInternal = emitInternal;
135
233
  // Recover uncommitted events from WAL
136
234
  if (config.wal?.enabled && config.wal?.recovery_on_startup !== false) {
137
235
  try {
@@ -154,8 +252,11 @@ async function initWithConfig(config, failFast) {
154
252
  }
155
253
  }
156
254
  }
255
+ state.traceStartUnregister = (0, context_1.registerTraceStartHandler)((span) => {
256
+ handleTraceStart(span);
257
+ });
157
258
  state.traceCompletionUnregister = (0, context_1.registerTraceCompletionHandler)((span) => {
158
- handleTraceCompletion(span.traceId);
259
+ handleTraceCompletion(span);
159
260
  });
160
261
  // Initialize telemetry/metrics collection
161
262
  await (0, telemetry_1.initMetrics)({ telemetry: config.telemetry });
@@ -177,6 +278,19 @@ async function initWithConfig(config, failFast) {
177
278
  }
178
279
  }
179
280
  dispatcher.start();
281
+ applyAttributionConfig(config);
282
+ const envValue = config.defaults?.environment || undefined;
283
+ if (options?.promptAttribution) {
284
+ await (0, attribution_1.promptForRegistration)({
285
+ environment: envValue,
286
+ });
287
+ }
288
+ else {
289
+ // Show first-run registration prompt if needed (one-time only)
290
+ (0, attribution_1.showFirstRunPromptIfNeeded)({
291
+ environment: envValue,
292
+ });
293
+ }
180
294
  emitSdkInit(state);
181
295
  }
182
296
  function getSdkVersion() {
@@ -217,6 +331,166 @@ function emitSdkInit(current) {
217
331
  logger_1.logger.error('Failed to emit sdk_init event: %s', error);
218
332
  }
219
333
  }
334
+ function applyAttributionConfig(config) {
335
+ const attribution = config.attribution || {};
336
+ const telemetry = attribution.telemetry || {};
337
+ (0, attribution_1.configureHttp)({
338
+ endpoints: telemetry.endpoints
339
+ ? {
340
+ us: telemetry.endpoints.us ?? undefined,
341
+ eu: telemetry.endpoints.eu ?? undefined,
342
+ }
343
+ : undefined,
344
+ apiKeyEnv: telemetry.api_key_env ?? undefined,
345
+ timeoutSec: typeof telemetry.timeout_sec === 'number' ? telemetry.timeout_sec : undefined,
346
+ retryAttempts: typeof telemetry.retry_attempts === 'number' ? telemetry.retry_attempts : undefined,
347
+ backoffBaseSec: typeof telemetry.backoff_base_sec === 'number' ? telemetry.backoff_base_sec : undefined,
348
+ });
349
+ if (telemetry.enabled || telemetry.send_data || attribution.enabled) {
350
+ (0, attribution_1.enableTelemetry)({
351
+ sendData: Boolean(telemetry.send_data),
352
+ dataResidency: telemetry.data_residency ?? null,
353
+ });
354
+ }
355
+ const registration = normalizeRegistration(attribution.project || {});
356
+ if (hasNonEmptyValues(registration)) {
357
+ try {
358
+ (0, attribution_1.registerProject)(registration);
359
+ }
360
+ catch {
361
+ // Ignore registration errors
362
+ }
363
+ }
364
+ const audit = normalizeAuditMetadata(config.audit || {});
365
+ if (hasAuditValues(audit)) {
366
+ try {
367
+ (0, attribution_1.setAuditMetadata)(audit);
368
+ }
369
+ catch {
370
+ // Ignore audit metadata errors
371
+ }
372
+ }
373
+ recordFeatureFlags(config, attribution);
374
+ if (telemetry.enabled || telemetry.send_data) {
375
+ try {
376
+ (0, attribution_1.reportUsage)({ environment: config.defaults?.environment || 'development' });
377
+ }
378
+ catch {
379
+ // Ignore telemetry errors
380
+ }
381
+ }
382
+ }
383
+ function normalizeRegistration(project) {
384
+ return cleanConfigPayload({
385
+ company: project.company,
386
+ role: project.role,
387
+ email: project.email,
388
+ source: project.source,
389
+ useCase: project.useCase ?? project.use_case,
390
+ teamSize: project.teamSize ?? project.team_size,
391
+ businessOwner: project.businessOwner ?? project.business_owner,
392
+ dataCategories: project.dataCategories ?? project.data_categories,
393
+ tags: project.tags,
394
+ });
395
+ }
396
+ function normalizeAuditMetadata(audit) {
397
+ return cleanConfigPayload({
398
+ useCaseName: audit.useCaseName ?? audit.use_case_name,
399
+ businessOwner: audit.businessOwner ?? audit.business_owner,
400
+ dataCategories: audit.dataCategories ?? audit.data_categories,
401
+ riskLevel: audit.riskLevel ?? audit.risk_level,
402
+ complianceFrameworks: audit.complianceFrameworks ?? audit.compliance_frameworks,
403
+ reviewDate: audit.reviewDate ?? audit.review_date,
404
+ reviewer: audit.reviewer,
405
+ notes: audit.notes,
406
+ });
407
+ }
408
+ function cleanConfigPayload(payload) {
409
+ const cleaned = {};
410
+ for (const [key, value] of Object.entries(payload)) {
411
+ if (value === null || value === undefined) {
412
+ continue;
413
+ }
414
+ if (typeof value === 'string' && value.trim() === '') {
415
+ continue;
416
+ }
417
+ if (Array.isArray(value) && value.length === 0) {
418
+ continue;
419
+ }
420
+ if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) {
421
+ continue;
422
+ }
423
+ cleaned[key] = value;
424
+ }
425
+ return cleaned;
426
+ }
427
+ function hasNonEmptyValues(payload) {
428
+ return Object.keys(cleanConfigPayload(payload)).length > 0;
429
+ }
430
+ function hasAuditValues(payload) {
431
+ const cleaned = cleanConfigPayload(payload);
432
+ if (Object.keys(cleaned).length === 0) {
433
+ return false;
434
+ }
435
+ if (Object.keys(cleaned).length === 1 && cleaned.riskLevel) {
436
+ return false;
437
+ }
438
+ return true;
439
+ }
440
+ function recordFeatureFlags(config, attribution) {
441
+ const record = (name) => {
442
+ try {
443
+ (0, attribution_1.recordFeatureUsage)(name);
444
+ }
445
+ catch {
446
+ // Ignore feature usage errors
447
+ }
448
+ };
449
+ if (config.immutability?.enabled)
450
+ record('immutability');
451
+ if (config.policies?.enforce)
452
+ record('policy_enforcement');
453
+ if (config.data_handling?.enabled)
454
+ record('data_handling');
455
+ if (config.reporting?.enabled)
456
+ record('reporting');
457
+ if (config.wal?.enabled)
458
+ record('wal');
459
+ if (config.signing?.enabled)
460
+ record('signing');
461
+ if (config.attestation?.enabled)
462
+ record('attestation');
463
+ if (config.ai_act?.enabled)
464
+ record('ai_act');
465
+ if (config.instrumentation?.enabled)
466
+ record('instrumentation');
467
+ if (config.telemetry?.enabled)
468
+ record('metrics_telemetry');
469
+ if (attribution.telemetry?.enabled)
470
+ record('usage_telemetry');
471
+ for (const sink of config.sinks || []) {
472
+ const sinkType = sink && typeof sink === 'object'
473
+ ? String(sink.type || '').toLowerCase()
474
+ : String(sink || '').toLowerCase();
475
+ if (sinkType) {
476
+ record(`sink_${sinkType}`);
477
+ }
478
+ if (sink && typeof sink === 'object') {
479
+ const circuit = sink.circuit_breaker;
480
+ if (circuit && typeof circuit === 'object' && circuit.enabled) {
481
+ record('circuit_breaker');
482
+ }
483
+ const retryQueue = sink.retry_queue;
484
+ if (retryQueue && typeof retryQueue === 'object' && retryQueue.enabled) {
485
+ record('retry_queue');
486
+ }
487
+ const idempotency = sink.idempotency;
488
+ if (idempotency && typeof idempotency === 'object' && idempotency.enabled) {
489
+ record('idempotency');
490
+ }
491
+ }
492
+ }
493
+ }
220
494
  /**
221
495
  * Shutdown Monora with automatic chain verification.
222
496
  *
@@ -224,11 +498,26 @@ function emitSdkInit(current) {
224
498
  * 1. Verifies all pending trace chains (if verify_on_shutdown enabled)
225
499
  * 2. Finalizes all pending traces and emits report summaries
226
500
  * 3. Closes all sinks and dispatchers
501
+ *
502
+ * @remarks
503
+ * Cross-SDK Parity: This function is async (returns Promise) in Node.js but
504
+ * synchronous in Python. This is an intentional language difference:
505
+ * - Node.js: `await monora.shutdown()`
506
+ * - Python: `monora.shutdown()`
227
507
  */
228
508
  async function shutdown() {
229
509
  if (!state) {
230
510
  return;
231
511
  }
512
+ // Send shutdown telemetry event (opt-in only)
513
+ if ((0, attribution_1.isTelemetryEnabled)()) {
514
+ (0, attribution_1.sendTelemetryEvent)({
515
+ eventType: 'shutdown',
516
+ eventData: {
517
+ service_name: state.config.defaults?.service_name,
518
+ },
519
+ });
520
+ }
232
521
  // AUTOMATIC VERIFICATION ON SHUTDOWN
233
522
  const immutability = state.config.immutability || {};
234
523
  if (immutability.verify_on_shutdown !== false) {
@@ -237,6 +526,9 @@ async function shutdown() {
237
526
  if (state.traceCompletionUnregister) {
238
527
  state.traceCompletionUnregister();
239
528
  }
529
+ if (state.traceStartUnregister) {
530
+ state.traceStartUnregister();
531
+ }
240
532
  state.dispatcher.close();
241
533
  // Cleanup and close WAL
242
534
  if (state.config.wal?.enabled) {
@@ -273,7 +565,22 @@ function exportTrustPackage(traceId, options) {
273
565
  if (!events) {
274
566
  throw new Error('No events found for trust package; provide events, inputPath, or configure a file sink.');
275
567
  }
276
- let trustPackage = (0, trust_package_1.buildTrustPackage)(traceId, events, config);
568
+ let trustPackage = (0, trust_package_1.buildTrustPackage)(traceId, events, config, {
569
+ evidenceManifest: options?.evidenceManifest,
570
+ evidenceManifestPath: options?.evidenceManifestPath,
571
+ evidenceManifestAuto: options?.evidenceManifestAuto,
572
+ evidenceManifestStandard: options?.evidenceManifestStandard,
573
+ evidenceManifestIncludeLineage: options?.evidenceManifestIncludeLineage,
574
+ evidenceManifestIncludeHashChain: options?.evidenceManifestIncludeHashChain,
575
+ evidenceManifestIncludeWorkflowState: options?.evidenceManifestIncludeWorkflowState,
576
+ evidenceManifestIncludeAimsState: options?.evidenceManifestIncludeAimsState,
577
+ controlCatalog: options?.controlCatalog,
578
+ controlCatalogPath: options?.controlCatalogPath,
579
+ controlWorkflowState: options?.controlWorkflowState,
580
+ controlWorkflowStatePath: options?.controlWorkflowStatePath,
581
+ controlCoverageTarget: options?.controlCoverageTarget,
582
+ controlCoveragePath: options?.controlCoveragePath,
583
+ });
277
584
  if (options?.sign || options?.gpgKey || options?.gpgHome) {
278
585
  trustPackage = (0, trust_package_1.applyGpgSignature)(trustPackage, {
279
586
  gpgKey: options?.gpgKey,
@@ -366,6 +673,13 @@ function ensureState() {
366
673
  }
367
674
  return state;
368
675
  }
676
+ /**
677
+ * Get the current Monora state, or null if not initialized.
678
+ * Use this for optional event emission where initialization isn't required.
679
+ */
680
+ function getState() {
681
+ return state;
682
+ }
369
683
  function isRecoveredEventValid(event, config) {
370
684
  if (!event || typeof event !== 'object') {
371
685
  return false;
@@ -448,8 +762,6 @@ function emitEvent(event, options) {
448
762
  const status = typeof body.status === 'string' ? body.status : 'unknown';
449
763
  (0, telemetry_1.recordApiCall)(eventType, status);
450
764
  }
451
- // Publish to subscribers for real-time monitoring
452
- (0, streaming_1.publishEvent)(event);
453
765
  // Record event metric
454
766
  (0, telemetry_1.recordEvent)(eventType, 'success');
455
767
  current.dispatcher.emit(event);
@@ -459,7 +771,7 @@ function emitEvent(event, options) {
459
771
  }
460
772
  return;
461
773
  }
462
- if ((0, context_1.getCurrentSpan)() || recovered) {
774
+ if ((0, context_1.getCurrentSpan)()) {
463
775
  try {
464
776
  current.reportManager.recordEvent(event);
465
777
  }
@@ -467,10 +779,40 @@ function emitEvent(event, options) {
467
779
  logger_1.logger.error('Report recorder error: %s', error);
468
780
  }
469
781
  }
470
- // Publish to subscribers for real-time monitoring
471
- (0, streaming_1.publishEvent)(event);
472
782
  current.dispatcher.emit(event);
473
783
  }
784
+ function emitInternal(payload, body, options) {
785
+ const current = getState();
786
+ if (!current) {
787
+ return null;
788
+ }
789
+ const raw = typeof payload === 'object' && payload ? payload : {};
790
+ const eventType = (typeof payload === 'string' ? payload : undefined) ||
791
+ raw.event_type ||
792
+ 'custom';
793
+ const eventBody = body !== undefined ? body : (raw.body || {});
794
+ const normalizedBody = eventBody && typeof eventBody === 'object' && !Array.isArray(eventBody)
795
+ ? eventBody
796
+ : { value: eventBody };
797
+ const built = current.eventBuilder.build(eventType, normalizedBody, {
798
+ dataClassification: options?.dataClassification ||
799
+ raw.data_classification ||
800
+ current.config.defaults?.data_classification ||
801
+ 'internal',
802
+ purpose: options?.purpose ||
803
+ raw.purpose ||
804
+ current.config.defaults?.purpose ||
805
+ 'governance',
806
+ reason: options?.reason || raw.reason || undefined,
807
+ parentEventId: options?.parentEventId || raw.parent_event_id || undefined,
808
+ });
809
+ const traceId = options?.traceId || raw.trace_id;
810
+ if (traceId) {
811
+ built.trace_id = traceId;
812
+ }
813
+ emitEvent(built);
814
+ return built;
815
+ }
474
816
  /**
475
817
  * Verify event hash in real-time as it's emitted.
476
818
  *
@@ -478,8 +820,10 @@ function emitEvent(event, options) {
478
820
  * Failures are logged as warnings (user decision: don't block).
479
821
  */
480
822
  function verifyEventRealTime(event, expectedPrevHash) {
481
- // Lazy import to avoid circular dependency
482
- const { verifyEventHash } = require('./verify');
823
+ const verifyEventHash = getVerifyEventHash();
824
+ if (!verifyEventHash) {
825
+ return;
826
+ }
483
827
  try {
484
828
  if (!verifyEventHash(event)) {
485
829
  logger_1.logger.error('Real-time verification FAILED: event %s has invalid hash', event.event_id);
@@ -494,8 +838,8 @@ function verifyEventRealTime(event, expectedPrevHash) {
494
838
  }
495
839
  function notifyViolation(violation) {
496
840
  const current = ensureState();
497
- // Record violation metric
498
- const policyType = violation.policyName || 'model_policy';
841
+ // Record violation metric (policyType matches Python SDK's policy_type attribute)
842
+ const policyType = violation.policyType || 'model_policy';
499
843
  const model = violation.model || 'unknown';
500
844
  (0, telemetry_1.recordViolation)(policyType, model);
501
845
  if (!current.violationHandler) {
@@ -508,14 +852,43 @@ function notifyViolation(violation) {
508
852
  logger_1.logger.error('Violation handler error: %s', error);
509
853
  }
510
854
  }
511
- function handleTraceCompletion(traceId) {
855
+ function emitSpanEvent(current, span, eventType, durationMs) {
856
+ const tracingConfig = current.config.tracing || {};
857
+ if (tracingConfig.emit_span_events === false) {
858
+ return;
859
+ }
860
+ const body = {
861
+ name: span.name,
862
+ metadata: span.metadata,
863
+ };
864
+ if (durationMs !== undefined) {
865
+ body.duration_ms = durationMs;
866
+ }
867
+ const event = current.eventBuilder.build(eventType, body);
868
+ emitEvent(event);
869
+ }
870
+ function handleTraceStart(span) {
512
871
  const current = state;
513
- if (!current || !current.reportManager.isEnabled()) {
872
+ if (!current) {
873
+ return;
874
+ }
875
+ emitSpanEvent(current, span, 'span_start');
876
+ }
877
+ function handleTraceCompletion(span) {
878
+ const current = state;
879
+ if (!current) {
880
+ return;
881
+ }
882
+ const traceStart = (0, context_1.getTraceStartTime)(span.traceId);
883
+ const durationMs = traceStart !== undefined ? Math.max(0, Date.now() - traceStart) : undefined;
884
+ emitSpanEvent(current, span, 'span_end', durationMs);
885
+ current.quotaManager.removeTrace(span.traceId);
886
+ if (!current.reportManager.isEnabled()) {
514
887
  return;
515
888
  }
516
889
  try {
517
890
  const registryMetadata = buildRegistryMetadata(current.registry);
518
- const result = current.reportManager.finalizeTrace(traceId, registryMetadata);
891
+ const result = current.reportManager.finalizeTrace(span.traceId, registryMetadata);
519
892
  if (!result) {
520
893
  return;
521
894
  }
@@ -576,6 +949,8 @@ function executeCall(current, fn, args, config, context) {
576
949
  const purpose = options.purpose || current.config.defaults?.purpose || 'general';
577
950
  const reason = options.reason;
578
951
  const resolvedModel = options.model || resolveModelFromArgs(args, current.registry);
952
+ const span = (0, context_1.getCurrentSpan)();
953
+ const traceId = span ? span.traceId : null;
579
954
  if (config.eventType === 'tool_call' && options.toolName) {
580
955
  try {
581
956
  const violation = current.toolPolicyEngine.checkTool(options.toolName);
@@ -691,6 +1066,9 @@ function executeCall(current, fn, args, config, context) {
691
1066
  throw violation;
692
1067
  }
693
1068
  }
1069
+ if (config.eventType === 'llm_call') {
1070
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason);
1071
+ }
694
1072
  const startTime = perf_hooks_1.performance.now();
695
1073
  const stepNumber = config.eventType === 'agent_step' ? (0, context_1.nextStepNumber)() : undefined;
696
1074
  try {
@@ -720,9 +1098,17 @@ function executeCall(current, fn, args, config, context) {
720
1098
  reason,
721
1099
  });
722
1100
  emitEvent(event);
1101
+ if (config.eventType === 'llm_call') {
1102
+ const { tokens, usage } = extractUsageMetrics(response);
1103
+ const cost = estimateUsageCost(current, resolvedModel, usage, tokens);
1104
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, tokens || [0, 0], cost ?? undefined);
1105
+ }
723
1106
  return response;
724
1107
  })
725
1108
  .catch((error) => {
1109
+ if (error instanceof quotas_1.QuotaExceededError) {
1110
+ throw error;
1111
+ }
726
1112
  const durationMs = perf_hooks_1.performance.now() - startTime;
727
1113
  const logErrors = current.config.error_handling?.log_user_exceptions !== false;
728
1114
  const body = config.buildBody({
@@ -746,6 +1132,9 @@ function executeCall(current, fn, args, config, context) {
746
1132
  reason,
747
1133
  });
748
1134
  emitEvent(event);
1135
+ if (config.eventType === 'llm_call') {
1136
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, [0, 0], undefined, false);
1137
+ }
749
1138
  throw error;
750
1139
  });
751
1140
  }
@@ -771,10 +1160,17 @@ function executeCall(current, fn, args, config, context) {
771
1160
  reason,
772
1161
  });
773
1162
  emitEvent(event);
1163
+ if (config.eventType === 'llm_call') {
1164
+ const { tokens, usage } = extractUsageMetrics(result);
1165
+ const cost = estimateUsageCost(current, resolvedModel, usage, tokens);
1166
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, tokens || [0, 0], cost ?? undefined);
1167
+ }
774
1168
  return result;
775
1169
  }
776
1170
  catch (error) {
777
- if (error instanceof data_handling_1.DataHandlingViolation || error instanceof policy_1.PolicyViolation) {
1171
+ if (error instanceof data_handling_1.DataHandlingViolation ||
1172
+ error instanceof policy_1.PolicyViolation ||
1173
+ error instanceof quotas_1.QuotaExceededError) {
778
1174
  throw error;
779
1175
  }
780
1176
  const durationMs = perf_hooks_1.performance.now() - startTime;
@@ -800,6 +1196,9 @@ function executeCall(current, fn, args, config, context) {
800
1196
  reason,
801
1197
  });
802
1198
  emitEvent(event);
1199
+ if (config.eventType === 'llm_call') {
1200
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, [0, 0], undefined, false);
1201
+ }
803
1202
  throw error;
804
1203
  }
805
1204
  }
@@ -851,6 +1250,48 @@ function emitRegistryOverride(current, model, dataClassification, purpose, reaso
851
1250
  emitEvent(event);
852
1251
  return event.event_id || null;
853
1252
  }
1253
+ function applyQuotaLimits(current, traceId, model, dataClassification, purpose, reason, tokens, cost, raiseOnBlock = true) {
1254
+ if (!traceId || !current.quotaManager.isEnabled()) {
1255
+ return;
1256
+ }
1257
+ const tracker = current.quotaManager.getTracker(traceId);
1258
+ if (tokens || cost !== undefined) {
1259
+ const [promptTokens, completionTokens] = tokens || [0, 0];
1260
+ tracker.recordTokenUsage(promptTokens, completionTokens, cost);
1261
+ }
1262
+ const result = tracker.checkQuotas();
1263
+ if (result.allowed) {
1264
+ return;
1265
+ }
1266
+ emitQuotaViolation(current.eventBuilder, model, result, dataClassification, purpose, reason);
1267
+ const violation = new quotas_1.QuotaExceededError(result.exceededQuota || 'unknown', result.currentValue || 0, result.limitValue || 0, model);
1268
+ notifyViolation(violation);
1269
+ const action = tracker.getConfig().action;
1270
+ if (action === 'block' && raiseOnBlock) {
1271
+ throw violation;
1272
+ }
1273
+ logger_1.logger.warning('Quota warning: %s', result.message);
1274
+ }
1275
+ function emitQuotaViolation(builder, model, result, dataClassification, purpose, reason) {
1276
+ const body = {
1277
+ model,
1278
+ policy_name: `quota.${result.exceededQuota || 'unknown'}`,
1279
+ message: result.message,
1280
+ status: 'policy_violation',
1281
+ error: null,
1282
+ quota: {
1283
+ type: result.exceededQuota ?? null,
1284
+ current_value: result.currentValue ?? null,
1285
+ limit_value: result.limitValue ?? null,
1286
+ },
1287
+ };
1288
+ const event = builder.build('llm_call', body, {
1289
+ dataClassification,
1290
+ purpose,
1291
+ reason,
1292
+ });
1293
+ emitEvent(event);
1294
+ }
854
1295
  function buildLlmBody(params) {
855
1296
  const responseDetails = params.response ? extractResponseDetails(params.response) : null;
856
1297
  let explanation;
@@ -922,6 +1363,132 @@ function extractResponseDetails(response) {
922
1363
  }
923
1364
  return { content, finish_reason: finishReason, usage };
924
1365
  }
1366
+ function extractUsageMetrics(response) {
1367
+ if (!response) {
1368
+ return { tokens: null, usage: null };
1369
+ }
1370
+ const details = extractResponseDetails(response);
1371
+ const usage = details && typeof details === 'object' ? details.usage : null;
1372
+ return { tokens: normalizeTokenUsage(usage), usage };
1373
+ }
1374
+ function estimateUsageCost(current, model, usage, tokens) {
1375
+ if (!usage || typeof usage !== 'object') {
1376
+ return null;
1377
+ }
1378
+ for (const key of ['total_cost', 'cost', 'estimated_cost', 'total_cost_usd', 'cost_usd']) {
1379
+ const value = usage[key];
1380
+ const cost = coerceCost(value);
1381
+ if (cost !== null) {
1382
+ return cost;
1383
+ }
1384
+ }
1385
+ if (!model || !tokens) {
1386
+ return null;
1387
+ }
1388
+ const metadata = current.registry.getModelMetadata(model);
1389
+ const pricing = metadata && typeof metadata === 'object' ? metadata.pricing : null;
1390
+ return estimateCostFromPricing(pricing, tokens);
1391
+ }
1392
+ function estimateCostFromPricing(pricing, tokens) {
1393
+ if (!pricing || typeof pricing !== 'object') {
1394
+ return null;
1395
+ }
1396
+ const [promptTokens, completionTokens] = tokens;
1397
+ const pickRatePerToken = (candidates) => {
1398
+ for (const candidate of candidates) {
1399
+ if (!(candidate.key in pricing)) {
1400
+ continue;
1401
+ }
1402
+ const cost = coerceCost(pricing[candidate.key]);
1403
+ if (cost === null) {
1404
+ continue;
1405
+ }
1406
+ return cost / candidate.scale;
1407
+ }
1408
+ return null;
1409
+ };
1410
+ const totalRate = pickRatePerToken([
1411
+ { key: 'cost_per_token', scale: 1 },
1412
+ { key: 'cost_per_1k_tokens', scale: 1000 },
1413
+ { key: 'total_cost_per_1k', scale: 1000 },
1414
+ { key: 'cost_per_1m_tokens', scale: 1000000 },
1415
+ { key: 'total_cost_per_1m', scale: 1000000 },
1416
+ ]);
1417
+ const promptRate = pickRatePerToken([
1418
+ { key: 'prompt_cost_per_token', scale: 1 },
1419
+ { key: 'input_cost_per_token', scale: 1 },
1420
+ { key: 'prompt_cost_per_1k', scale: 1000 },
1421
+ { key: 'input_cost_per_1k', scale: 1000 },
1422
+ { key: 'prompt_cost_per_1m', scale: 1000000 },
1423
+ { key: 'input_cost_per_1m', scale: 1000000 },
1424
+ ]);
1425
+ const completionRate = pickRatePerToken([
1426
+ { key: 'completion_cost_per_token', scale: 1 },
1427
+ { key: 'output_cost_per_token', scale: 1 },
1428
+ { key: 'completion_cost_per_1k', scale: 1000 },
1429
+ { key: 'output_cost_per_1k', scale: 1000 },
1430
+ { key: 'completion_cost_per_1m', scale: 1000000 },
1431
+ { key: 'output_cost_per_1m', scale: 1000000 },
1432
+ ]);
1433
+ if (totalRate !== null) {
1434
+ return (promptTokens + completionTokens) * totalRate;
1435
+ }
1436
+ if (promptRate === null && completionRate === null) {
1437
+ return null;
1438
+ }
1439
+ let cost = 0;
1440
+ if (promptRate !== null) {
1441
+ cost += promptTokens * promptRate;
1442
+ }
1443
+ if (completionRate !== null) {
1444
+ cost += completionTokens * completionRate;
1445
+ }
1446
+ return cost;
1447
+ }
1448
+ function normalizeTokenUsage(usage) {
1449
+ if (!usage || typeof usage !== 'object') {
1450
+ return null;
1451
+ }
1452
+ let prompt = coerceToken(usage.prompt_tokens);
1453
+ let completion = coerceToken(usage.completion_tokens);
1454
+ if (prompt === null) {
1455
+ prompt = coerceToken(usage.input_tokens);
1456
+ }
1457
+ if (completion === null) {
1458
+ completion = coerceToken(usage.output_tokens);
1459
+ }
1460
+ const total = coerceToken(usage.total_tokens);
1461
+ if (prompt === null && completion === null && total === null) {
1462
+ return null;
1463
+ }
1464
+ if (prompt === null) {
1465
+ prompt = Math.max(0, (total || 0) - (completion || 0));
1466
+ }
1467
+ if (completion === null) {
1468
+ completion = Math.max(0, (total || 0) - (prompt || 0));
1469
+ }
1470
+ return [prompt || 0, completion || 0];
1471
+ }
1472
+ function coerceToken(value) {
1473
+ if (value === null || value === undefined) {
1474
+ return null;
1475
+ }
1476
+ const number = Number(value);
1477
+ if (!Number.isFinite(number)) {
1478
+ return null;
1479
+ }
1480
+ return Math.trunc(number);
1481
+ }
1482
+ function coerceCost(value) {
1483
+ if (value === null || value === undefined) {
1484
+ return null;
1485
+ }
1486
+ const number = Number(value);
1487
+ if (!Number.isFinite(number)) {
1488
+ return null;
1489
+ }
1490
+ return number;
1491
+ }
925
1492
  function formatError(error) {
926
1493
  return { type: error.name || 'Error', message: error.message || String(error) };
927
1494
  }
@@ -1001,7 +1568,7 @@ function buildSinks(sinkConfigs, failFast) {
1001
1568
  const sinkType = String(config.type || '').toLowerCase();
1002
1569
  try {
1003
1570
  if (sinkType === 'stdout') {
1004
- sinks.push(new sinks_1.StdoutSink(config.format || 'json'));
1571
+ sinks.push(new sinks_1.StdoutSink(config.format || 'pretty'));
1005
1572
  }
1006
1573
  else if (sinkType === 'file') {
1007
1574
  sinks.push(new sinks_1.FileSink(config.path, {
@@ -1009,9 +1576,10 @@ function buildSinks(sinkConfigs, failFast) {
1009
1576
  flushIntervalSec: config.flush_interval_sec || 5.0,
1010
1577
  rotation: config.rotation || 'none',
1011
1578
  maxSizeMb: config.max_size_mb,
1579
+ symlink: config.symlink,
1012
1580
  }));
1013
1581
  }
1014
- else if (sinkType === 'https') {
1582
+ else if (sinkType === 'https' || sinkType === 'http') {
1015
1583
  const headers = expandHeaders(config.headers || {});
1016
1584
  const retryQueueConfig = config.retry_queue
1017
1585
  ? {
@@ -1027,6 +1595,12 @@ function buildSinks(sinkConfigs, failFast) {
1027
1595
  headerName: config.idempotency.header_name,
1028
1596
  }
1029
1597
  : undefined;
1598
+ const dataResidencyConfig = config.data_residency
1599
+ ? {
1600
+ residency: config.data_residency,
1601
+ action: config.data_residency_action,
1602
+ }
1603
+ : undefined;
1030
1604
  sinks.push(new sinks_1.HttpSink(config.endpoint, headers, {
1031
1605
  batchSize: config.batch_size || 50,
1032
1606
  timeoutSec: config.timeout_sec || 10.0,
@@ -1035,6 +1609,7 @@ function buildSinks(sinkConfigs, failFast) {
1035
1609
  circuitBreaker: config.circuit_breaker,
1036
1610
  retryQueue: retryQueueConfig,
1037
1611
  idempotency: idempotencyConfig,
1612
+ dataResidency: dataResidencyConfig,
1038
1613
  }));
1039
1614
  }
1040
1615
  else if (sinkType) {
@@ -1073,5 +1648,6 @@ function findEventSource(config) {
1073
1648
  return String(sink.path);
1074
1649
  }
1075
1650
  }
1076
- return config.error_handling?.fallback_path;
1651
+ const fallback = config.error_handling?.fallback_path;
1652
+ return fallback ? String(fallback) : undefined;
1077
1653
  }