monora-ai 2.1.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 (184) hide show
  1. package/README.md +333 -159
  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 +85 -0
  12. package/dist/assessment.d.ts.map +1 -1
  13. package/dist/assessment.js +506 -13
  14. package/dist/attribution.d.ts +44 -3
  15. package/dist/attribution.d.ts.map +1 -1
  16. package/dist/attribution.js +197 -10
  17. package/dist/autodetect.d.ts +68 -0
  18. package/dist/autodetect.d.ts.map +1 -1
  19. package/dist/autodetect.js +639 -0
  20. package/dist/bias.d.ts +130 -0
  21. package/dist/bias.d.ts.map +1 -0
  22. package/dist/bias.js +223 -0
  23. package/dist/cli/diagnostics.d.ts +5 -1
  24. package/dist/cli/diagnostics.d.ts.map +1 -1
  25. package/dist/cli/diagnostics.js +23 -6
  26. package/dist/cli/doctor.d.ts +25 -0
  27. package/dist/cli/doctor.d.ts.map +1 -0
  28. package/dist/cli/doctor.js +381 -0
  29. package/dist/cli/fix.d.ts +16 -0
  30. package/dist/cli/fix.d.ts.map +1 -0
  31. package/dist/cli/fix.js +284 -0
  32. package/dist/cli/init.d.ts +57 -0
  33. package/dist/cli/init.d.ts.map +1 -0
  34. package/dist/cli/init.js +205 -0
  35. package/dist/cli.js +1550 -176
  36. package/dist/complianceTargets.d.ts +111 -0
  37. package/dist/complianceTargets.d.ts.map +1 -0
  38. package/dist/complianceTargets.js +521 -0
  39. package/dist/config.d.ts +261 -16
  40. package/dist/config.d.ts.map +1 -1
  41. package/dist/config.js +381 -32
  42. package/dist/config_migrations.d.ts.map +1 -1
  43. package/dist/config_migrations.js +38 -1
  44. package/dist/config_schema.d.ts +2490 -1035
  45. package/dist/config_schema.d.ts.map +1 -1
  46. package/dist/config_schema.js +233 -64
  47. package/dist/context.d.ts +34 -0
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/context.js +118 -7
  50. package/dist/control_backbone.d.ts +122 -0
  51. package/dist/control_backbone.d.ts.map +1 -0
  52. package/dist/control_backbone.js +698 -0
  53. package/dist/data-governance.d.ts +187 -0
  54. package/dist/data-governance.d.ts.map +1 -0
  55. package/dist/data-governance.js +424 -0
  56. package/dist/dataResidency.d.ts +44 -0
  57. package/dist/dataResidency.d.ts.map +1 -0
  58. package/dist/dataResidency.js +203 -0
  59. package/dist/dispatcher.d.ts.map +1 -1
  60. package/dist/dispatcher.js +17 -5
  61. package/dist/evidence_store.d.ts +103 -0
  62. package/dist/evidence_store.d.ts.map +1 -0
  63. package/dist/evidence_store.js +459 -0
  64. package/dist/executiveSummary.d.ts +15 -0
  65. package/dist/executiveSummary.d.ts.map +1 -1
  66. package/dist/executiveSummary.js +135 -22
  67. package/dist/identity.d.ts +143 -0
  68. package/dist/identity.d.ts.map +1 -0
  69. package/dist/identity.js +231 -0
  70. package/dist/impact-assessment.d.ts +350 -0
  71. package/dist/impact-assessment.d.ts.map +1 -0
  72. package/dist/impact-assessment.js +580 -0
  73. package/dist/index.d.ts +20 -4
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +247 -5
  76. package/dist/instrumentation.d.ts +1 -1
  77. package/dist/instrumentation.d.ts.map +1 -1
  78. package/dist/instrumentation.js +123 -22
  79. package/dist/integrations/anthropic.d.ts +3 -0
  80. package/dist/integrations/anthropic.d.ts.map +1 -1
  81. package/dist/integrations/anthropic.js +282 -80
  82. package/dist/integrations/governance.d.ts +33 -0
  83. package/dist/integrations/governance.d.ts.map +1 -0
  84. package/dist/integrations/governance.js +208 -0
  85. package/dist/integrations/langchain.d.ts +4 -0
  86. package/dist/integrations/langchain.d.ts.map +1 -1
  87. package/dist/integrations/langchain.js +362 -142
  88. package/dist/integrations/openai.d.ts +9 -0
  89. package/dist/integrations/openai.d.ts.map +1 -1
  90. package/dist/integrations/openai.js +673 -73
  91. package/dist/iso42001_consolidation.d.ts +16 -0
  92. package/dist/iso42001_consolidation.d.ts.map +1 -0
  93. package/dist/iso42001_consolidation.js +413 -0
  94. package/dist/iso42001_workflows.d.ts +263 -0
  95. package/dist/iso42001_workflows.d.ts.map +1 -0
  96. package/dist/iso42001_workflows.js +781 -0
  97. package/dist/lifecycle.d.ts +299 -0
  98. package/dist/lifecycle.d.ts.map +1 -0
  99. package/dist/lifecycle.js +624 -0
  100. package/dist/lineage.d.ts +2 -2
  101. package/dist/lineage.d.ts.map +1 -1
  102. package/dist/lineage.js +9 -16
  103. package/dist/middleware/express.d.ts.map +1 -1
  104. package/dist/middleware/express.js +18 -3
  105. package/dist/middleware/nextjs.js +2 -2
  106. package/dist/model.d.ts +143 -0
  107. package/dist/model.d.ts.map +1 -0
  108. package/dist/model.js +371 -0
  109. package/dist/onboarding.d.ts +42 -0
  110. package/dist/onboarding.d.ts.map +1 -0
  111. package/dist/onboarding.js +1022 -0
  112. package/dist/oversight.d.ts +264 -0
  113. package/dist/oversight.d.ts.map +1 -0
  114. package/dist/oversight.js +497 -0
  115. package/dist/presets.js +7 -7
  116. package/dist/quotas.d.ts +171 -0
  117. package/dist/quotas.d.ts.map +1 -0
  118. package/dist/quotas.js +259 -0
  119. package/dist/register.d.ts +13 -0
  120. package/dist/register.d.ts.map +1 -0
  121. package/dist/register.js +99 -0
  122. package/dist/registry.d.ts +1 -0
  123. package/dist/registry.d.ts.map +1 -1
  124. package/dist/registry.js +7 -0
  125. package/dist/registryData.json +43 -6
  126. package/dist/report.d.ts +2 -1
  127. package/dist/report.d.ts.map +1 -1
  128. package/dist/report.js +189 -2
  129. package/dist/reporting.d.ts +125 -0
  130. package/dist/reporting.d.ts.map +1 -1
  131. package/dist/reporting.js +192 -2
  132. package/dist/resources.d.ts +285 -0
  133. package/dist/resources.d.ts.map +1 -0
  134. package/dist/resources.js +643 -0
  135. package/dist/risk.d.ts +120 -0
  136. package/dist/risk.d.ts.map +1 -0
  137. package/dist/risk.js +220 -0
  138. package/dist/runtime.d.ts +73 -0
  139. package/dist/runtime.d.ts.map +1 -1
  140. package/dist/runtime.js +415 -18
  141. package/dist/schemaInference.d.ts +92 -0
  142. package/dist/schemaInference.d.ts.map +1 -0
  143. package/dist/schemaInference.js +466 -0
  144. package/dist/schema_validation.js +2 -2
  145. package/dist/schemas/config.schema.json +118 -4
  146. package/dist/security_report.js +4 -4
  147. package/dist/signing.d.ts +1 -1
  148. package/dist/signing.d.ts.map +1 -1
  149. package/dist/signing.js +4 -0
  150. package/dist/sinks/file.d.ts +19 -1
  151. package/dist/sinks/file.d.ts.map +1 -1
  152. package/dist/sinks/file.js +82 -13
  153. package/dist/sinks/https.d.ts +10 -0
  154. package/dist/sinks/https.d.ts.map +1 -1
  155. package/dist/sinks/https.js +76 -16
  156. package/dist/sinks/stdout.d.ts +1 -0
  157. package/dist/sinks/stdout.d.ts.map +1 -1
  158. package/dist/sinks/stdout.js +12 -1
  159. package/dist/spec.d.ts +159 -0
  160. package/dist/spec.d.ts.map +1 -0
  161. package/dist/spec.js +391 -0
  162. package/dist/stakeholders.d.ts +199 -0
  163. package/dist/stakeholders.d.ts.map +1 -0
  164. package/dist/stakeholders.js +398 -0
  165. package/dist/standards.d.ts.map +1 -1
  166. package/dist/standards.js +160 -2
  167. package/dist/standards_ingest.d.ts.map +1 -1
  168. package/dist/standards_ingest.js +1 -4
  169. package/dist/telemetry.d.ts +16 -2
  170. package/dist/telemetry.d.ts.map +1 -1
  171. package/dist/telemetry.js +77 -14
  172. package/dist/templates/controls/iso42001_control_catalog.json +1443 -0
  173. package/dist/traced_emitter.d.ts.map +1 -1
  174. package/dist/traced_emitter.js +19 -9
  175. package/dist/trust_package.d.ts +19 -1
  176. package/dist/trust_package.d.ts.map +1 -1
  177. package/dist/trust_package.js +89 -2
  178. package/dist/verify.d.ts.map +1 -1
  179. package/dist/verify.js +9 -2
  180. package/dist/wal.d.ts.map +1 -1
  181. package/dist/wal.js +2 -1
  182. package/package.json +14 -1
  183. package/scripts/postinstall.js +105 -210
  184. 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");
@@ -39,7 +45,26 @@ const instrumentation_1 = require("./instrumentation");
39
45
  const telemetry_1 = require("./telemetry");
40
46
  const attribution_1 = require("./attribution");
41
47
  const config_migrations_1 = require("./config_migrations");
48
+ const quotas_1 = require("./quotas");
42
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
+ }
43
68
  /**
44
69
  * Initialize Monora with simplified configuration.
45
70
  *
@@ -56,14 +81,25 @@ let state = null;
56
81
  * init({ configPath: 'monora.yml' }); // Load from config file
57
82
  * init({ configDict: {...} }); // Provide full config dict
58
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")`
59
90
  */
60
91
  async function init(options) {
61
- 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 || {};
62
93
  if (state) {
63
94
  if (state.traceCompletionUnregister) {
64
95
  state.traceCompletionUnregister();
65
96
  }
97
+ if (state.traceStartUnregister) {
98
+ state.traceStartUnregister();
99
+ }
66
100
  state.dispatcher.close();
101
+ // Clear any previously registered callbacks to avoid duplicates on re-init
102
+ (0, dispatcher_1.clearAuditCallbacks)();
67
103
  state = null;
68
104
  }
69
105
  // Auto-detect environment and service info
@@ -109,7 +145,23 @@ async function init(options) {
109
145
  }
110
146
  // Check for production readiness warnings
111
147
  checkProductionReadiness(config, effectivePreset);
112
- await initWithConfig(config, failFast);
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
+ }
113
165
  }
114
166
  /**
115
167
  * Check configuration for production readiness and log warnings.
@@ -130,10 +182,32 @@ function checkProductionReadiness(config, presetName) {
130
182
  (0, config_migrations_1.logProductionWarnings)(warnings);
131
183
  }
132
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.');
206
+ }
133
207
  /**
134
208
  * Initialize Monora with a pre-built configuration.
135
209
  */
136
- async function initWithConfig(config, failFast) {
210
+ async function initWithConfig(config, failFast, options) {
137
211
  const sinks = buildSinks(config.sinks || [], failFast);
138
212
  const dispatcher = new dispatcher_1.EventDispatcher(sinks, config);
139
213
  const registry = new registry_1.ModelRegistry(config.registry || {});
@@ -153,7 +227,9 @@ async function initWithConfig(config, failFast) {
153
227
  reportManager,
154
228
  wal,
155
229
  signer,
230
+ quotaManager: new quotas_1.QuotaManager(config.quotas || {}),
156
231
  };
232
+ state.emitInternal = emitInternal;
157
233
  // Recover uncommitted events from WAL
158
234
  if (config.wal?.enabled && config.wal?.recovery_on_startup !== false) {
159
235
  try {
@@ -176,8 +252,11 @@ async function initWithConfig(config, failFast) {
176
252
  }
177
253
  }
178
254
  }
255
+ state.traceStartUnregister = (0, context_1.registerTraceStartHandler)((span) => {
256
+ handleTraceStart(span);
257
+ });
179
258
  state.traceCompletionUnregister = (0, context_1.registerTraceCompletionHandler)((span) => {
180
- handleTraceCompletion(span.traceId);
259
+ handleTraceCompletion(span);
181
260
  });
182
261
  // Initialize telemetry/metrics collection
183
262
  await (0, telemetry_1.initMetrics)({ telemetry: config.telemetry });
@@ -200,6 +279,18 @@ async function initWithConfig(config, failFast) {
200
279
  }
201
280
  dispatcher.start();
202
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
+ }
203
294
  emitSdkInit(state);
204
295
  }
205
296
  function getSdkVersion() {
@@ -251,9 +342,9 @@ function applyAttributionConfig(config) {
251
342
  }
252
343
  : undefined,
253
344
  apiKeyEnv: telemetry.api_key_env ?? undefined,
254
- timeoutMs: typeof telemetry.timeout_sec === 'number' ? telemetry.timeout_sec * 1000 : undefined,
345
+ timeoutSec: typeof telemetry.timeout_sec === 'number' ? telemetry.timeout_sec : undefined,
255
346
  retryAttempts: typeof telemetry.retry_attempts === 'number' ? telemetry.retry_attempts : undefined,
256
- backoffBaseMs: typeof telemetry.backoff_base_sec === 'number' ? telemetry.backoff_base_sec * 1000 : undefined,
347
+ backoffBaseSec: typeof telemetry.backoff_base_sec === 'number' ? telemetry.backoff_base_sec : undefined,
257
348
  });
258
349
  if (telemetry.enabled || telemetry.send_data || attribution.enabled) {
259
350
  (0, attribution_1.enableTelemetry)({
@@ -407,11 +498,26 @@ function recordFeatureFlags(config, attribution) {
407
498
  * 1. Verifies all pending trace chains (if verify_on_shutdown enabled)
408
499
  * 2. Finalizes all pending traces and emits report summaries
409
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()`
410
507
  */
411
508
  async function shutdown() {
412
509
  if (!state) {
413
510
  return;
414
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
+ }
415
521
  // AUTOMATIC VERIFICATION ON SHUTDOWN
416
522
  const immutability = state.config.immutability || {};
417
523
  if (immutability.verify_on_shutdown !== false) {
@@ -420,6 +526,9 @@ async function shutdown() {
420
526
  if (state.traceCompletionUnregister) {
421
527
  state.traceCompletionUnregister();
422
528
  }
529
+ if (state.traceStartUnregister) {
530
+ state.traceStartUnregister();
531
+ }
423
532
  state.dispatcher.close();
424
533
  // Cleanup and close WAL
425
534
  if (state.config.wal?.enabled) {
@@ -456,7 +565,22 @@ function exportTrustPackage(traceId, options) {
456
565
  if (!events) {
457
566
  throw new Error('No events found for trust package; provide events, inputPath, or configure a file sink.');
458
567
  }
459
- 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
+ });
460
584
  if (options?.sign || options?.gpgKey || options?.gpgHome) {
461
585
  trustPackage = (0, trust_package_1.applyGpgSignature)(trustPackage, {
462
586
  gpgKey: options?.gpgKey,
@@ -549,6 +673,13 @@ function ensureState() {
549
673
  }
550
674
  return state;
551
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
+ }
552
683
  function isRecoveredEventValid(event, config) {
553
684
  if (!event || typeof event !== 'object') {
554
685
  return false;
@@ -640,7 +771,7 @@ function emitEvent(event, options) {
640
771
  }
641
772
  return;
642
773
  }
643
- if ((0, context_1.getCurrentSpan)() || recovered) {
774
+ if ((0, context_1.getCurrentSpan)()) {
644
775
  try {
645
776
  current.reportManager.recordEvent(event);
646
777
  }
@@ -650,6 +781,38 @@ function emitEvent(event, options) {
650
781
  }
651
782
  current.dispatcher.emit(event);
652
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
+ }
653
816
  /**
654
817
  * Verify event hash in real-time as it's emitted.
655
818
  *
@@ -657,8 +820,10 @@ function emitEvent(event, options) {
657
820
  * Failures are logged as warnings (user decision: don't block).
658
821
  */
659
822
  function verifyEventRealTime(event, expectedPrevHash) {
660
- // Lazy import to avoid circular dependency
661
- const { verifyEventHash } = require('./verify');
823
+ const verifyEventHash = getVerifyEventHash();
824
+ if (!verifyEventHash) {
825
+ return;
826
+ }
662
827
  try {
663
828
  if (!verifyEventHash(event)) {
664
829
  logger_1.logger.error('Real-time verification FAILED: event %s has invalid hash', event.event_id);
@@ -673,8 +838,8 @@ function verifyEventRealTime(event, expectedPrevHash) {
673
838
  }
674
839
  function notifyViolation(violation) {
675
840
  const current = ensureState();
676
- // Record violation metric
677
- 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';
678
843
  const model = violation.model || 'unknown';
679
844
  (0, telemetry_1.recordViolation)(policyType, model);
680
845
  if (!current.violationHandler) {
@@ -687,14 +852,43 @@ function notifyViolation(violation) {
687
852
  logger_1.logger.error('Violation handler error: %s', error);
688
853
  }
689
854
  }
690
- 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) {
871
+ const current = state;
872
+ if (!current) {
873
+ return;
874
+ }
875
+ emitSpanEvent(current, span, 'span_start');
876
+ }
877
+ function handleTraceCompletion(span) {
691
878
  const current = state;
692
- if (!current || !current.reportManager.isEnabled()) {
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()) {
693
887
  return;
694
888
  }
695
889
  try {
696
890
  const registryMetadata = buildRegistryMetadata(current.registry);
697
- const result = current.reportManager.finalizeTrace(traceId, registryMetadata);
891
+ const result = current.reportManager.finalizeTrace(span.traceId, registryMetadata);
698
892
  if (!result) {
699
893
  return;
700
894
  }
@@ -755,6 +949,8 @@ function executeCall(current, fn, args, config, context) {
755
949
  const purpose = options.purpose || current.config.defaults?.purpose || 'general';
756
950
  const reason = options.reason;
757
951
  const resolvedModel = options.model || resolveModelFromArgs(args, current.registry);
952
+ const span = (0, context_1.getCurrentSpan)();
953
+ const traceId = span ? span.traceId : null;
758
954
  if (config.eventType === 'tool_call' && options.toolName) {
759
955
  try {
760
956
  const violation = current.toolPolicyEngine.checkTool(options.toolName);
@@ -870,6 +1066,9 @@ function executeCall(current, fn, args, config, context) {
870
1066
  throw violation;
871
1067
  }
872
1068
  }
1069
+ if (config.eventType === 'llm_call') {
1070
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason);
1071
+ }
873
1072
  const startTime = perf_hooks_1.performance.now();
874
1073
  const stepNumber = config.eventType === 'agent_step' ? (0, context_1.nextStepNumber)() : undefined;
875
1074
  try {
@@ -899,9 +1098,17 @@ function executeCall(current, fn, args, config, context) {
899
1098
  reason,
900
1099
  });
901
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
+ }
902
1106
  return response;
903
1107
  })
904
1108
  .catch((error) => {
1109
+ if (error instanceof quotas_1.QuotaExceededError) {
1110
+ throw error;
1111
+ }
905
1112
  const durationMs = perf_hooks_1.performance.now() - startTime;
906
1113
  const logErrors = current.config.error_handling?.log_user_exceptions !== false;
907
1114
  const body = config.buildBody({
@@ -925,6 +1132,9 @@ function executeCall(current, fn, args, config, context) {
925
1132
  reason,
926
1133
  });
927
1134
  emitEvent(event);
1135
+ if (config.eventType === 'llm_call') {
1136
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, [0, 0], undefined, false);
1137
+ }
928
1138
  throw error;
929
1139
  });
930
1140
  }
@@ -950,10 +1160,17 @@ function executeCall(current, fn, args, config, context) {
950
1160
  reason,
951
1161
  });
952
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
+ }
953
1168
  return result;
954
1169
  }
955
1170
  catch (error) {
956
- 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) {
957
1174
  throw error;
958
1175
  }
959
1176
  const durationMs = perf_hooks_1.performance.now() - startTime;
@@ -979,6 +1196,9 @@ function executeCall(current, fn, args, config, context) {
979
1196
  reason,
980
1197
  });
981
1198
  emitEvent(event);
1199
+ if (config.eventType === 'llm_call') {
1200
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, [0, 0], undefined, false);
1201
+ }
982
1202
  throw error;
983
1203
  }
984
1204
  }
@@ -1030,6 +1250,48 @@ function emitRegistryOverride(current, model, dataClassification, purpose, reaso
1030
1250
  emitEvent(event);
1031
1251
  return event.event_id || null;
1032
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
+ }
1033
1295
  function buildLlmBody(params) {
1034
1296
  const responseDetails = params.response ? extractResponseDetails(params.response) : null;
1035
1297
  let explanation;
@@ -1101,6 +1363,132 @@ function extractResponseDetails(response) {
1101
1363
  }
1102
1364
  return { content, finish_reason: finishReason, usage };
1103
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
+ }
1104
1492
  function formatError(error) {
1105
1493
  return { type: error.name || 'Error', message: error.message || String(error) };
1106
1494
  }
@@ -1180,7 +1568,7 @@ function buildSinks(sinkConfigs, failFast) {
1180
1568
  const sinkType = String(config.type || '').toLowerCase();
1181
1569
  try {
1182
1570
  if (sinkType === 'stdout') {
1183
- sinks.push(new sinks_1.StdoutSink(config.format || 'json'));
1571
+ sinks.push(new sinks_1.StdoutSink(config.format || 'pretty'));
1184
1572
  }
1185
1573
  else if (sinkType === 'file') {
1186
1574
  sinks.push(new sinks_1.FileSink(config.path, {
@@ -1188,6 +1576,7 @@ function buildSinks(sinkConfigs, failFast) {
1188
1576
  flushIntervalSec: config.flush_interval_sec || 5.0,
1189
1577
  rotation: config.rotation || 'none',
1190
1578
  maxSizeMb: config.max_size_mb,
1579
+ symlink: config.symlink,
1191
1580
  }));
1192
1581
  }
1193
1582
  else if (sinkType === 'https' || sinkType === 'http') {
@@ -1206,6 +1595,12 @@ function buildSinks(sinkConfigs, failFast) {
1206
1595
  headerName: config.idempotency.header_name,
1207
1596
  }
1208
1597
  : undefined;
1598
+ const dataResidencyConfig = config.data_residency
1599
+ ? {
1600
+ residency: config.data_residency,
1601
+ action: config.data_residency_action,
1602
+ }
1603
+ : undefined;
1209
1604
  sinks.push(new sinks_1.HttpSink(config.endpoint, headers, {
1210
1605
  batchSize: config.batch_size || 50,
1211
1606
  timeoutSec: config.timeout_sec || 10.0,
@@ -1214,6 +1609,7 @@ function buildSinks(sinkConfigs, failFast) {
1214
1609
  circuitBreaker: config.circuit_breaker,
1215
1610
  retryQueue: retryQueueConfig,
1216
1611
  idempotency: idempotencyConfig,
1612
+ dataResidency: dataResidencyConfig,
1217
1613
  }));
1218
1614
  }
1219
1615
  else if (sinkType) {
@@ -1252,5 +1648,6 @@ function findEventSource(config) {
1252
1648
  return String(sink.path);
1253
1649
  }
1254
1650
  }
1255
- return config.error_handling?.fallback_path;
1651
+ const fallback = config.error_handling?.fallback_path;
1652
+ return fallback ? String(fallback) : undefined;
1256
1653
  }