monora-ai 2.1.0 → 2.1.4

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 (193) hide show
  1. package/README.md +339 -158
  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 +525 -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 +1564 -177
  36. package/dist/complianceConsolidation.d.ts +17 -0
  37. package/dist/complianceConsolidation.d.ts.map +1 -0
  38. package/dist/complianceConsolidation.js +68 -0
  39. package/dist/complianceTargets.d.ts +111 -0
  40. package/dist/complianceTargets.d.ts.map +1 -0
  41. package/dist/complianceTargets.js +521 -0
  42. package/dist/config.d.ts +261 -16
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +381 -32
  45. package/dist/config_migrations.d.ts.map +1 -1
  46. package/dist/config_migrations.js +38 -1
  47. package/dist/config_schema.d.ts +2490 -1035
  48. package/dist/config_schema.d.ts.map +1 -1
  49. package/dist/config_schema.js +233 -64
  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 +128 -0
  54. package/dist/control_backbone.d.ts.map +1 -0
  55. package/dist/control_backbone.js +826 -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.map +1 -1
  63. package/dist/dispatcher.js +17 -5
  64. package/dist/evidence_store.d.ts +103 -0
  65. package/dist/evidence_store.d.ts.map +1 -0
  66. package/dist/evidence_store.js +459 -0
  67. package/dist/executiveSummary.d.ts +15 -0
  68. package/dist/executiveSummary.d.ts.map +1 -1
  69. package/dist/executiveSummary.js +135 -22
  70. package/dist/identity.d.ts +143 -0
  71. package/dist/identity.d.ts.map +1 -0
  72. package/dist/identity.js +231 -0
  73. package/dist/impact-assessment.d.ts +350 -0
  74. package/dist/impact-assessment.d.ts.map +1 -0
  75. package/dist/impact-assessment.js +580 -0
  76. package/dist/index.d.ts +21 -4
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +254 -5
  79. package/dist/instrumentation.d.ts +1 -1
  80. package/dist/instrumentation.d.ts.map +1 -1
  81. package/dist/instrumentation.js +123 -22
  82. package/dist/integrations/anthropic.d.ts +3 -0
  83. package/dist/integrations/anthropic.d.ts.map +1 -1
  84. package/dist/integrations/anthropic.js +282 -80
  85. package/dist/integrations/governance.d.ts +33 -0
  86. package/dist/integrations/governance.d.ts.map +1 -0
  87. package/dist/integrations/governance.js +208 -0
  88. package/dist/integrations/langchain.d.ts +4 -0
  89. package/dist/integrations/langchain.d.ts.map +1 -1
  90. package/dist/integrations/langchain.js +362 -142
  91. package/dist/integrations/openai.d.ts +9 -0
  92. package/dist/integrations/openai.d.ts.map +1 -1
  93. package/dist/integrations/openai.js +673 -73
  94. package/dist/iso42001_consolidation.d.ts +16 -0
  95. package/dist/iso42001_consolidation.d.ts.map +1 -0
  96. package/dist/iso42001_consolidation.js +413 -0
  97. package/dist/iso42001_workflows.d.ts +263 -0
  98. package/dist/iso42001_workflows.d.ts.map +1 -0
  99. package/dist/iso42001_workflows.js +781 -0
  100. package/dist/lifecycle.d.ts +299 -0
  101. package/dist/lifecycle.d.ts.map +1 -0
  102. package/dist/lifecycle.js +624 -0
  103. package/dist/lineage.d.ts +2 -2
  104. package/dist/lineage.d.ts.map +1 -1
  105. package/dist/lineage.js +9 -16
  106. package/dist/middleware/express.d.ts.map +1 -1
  107. package/dist/middleware/express.js +18 -3
  108. package/dist/middleware/nextjs.js +2 -2
  109. package/dist/model.d.ts +143 -0
  110. package/dist/model.d.ts.map +1 -0
  111. package/dist/model.js +371 -0
  112. package/dist/onboarding.d.ts +42 -0
  113. package/dist/onboarding.d.ts.map +1 -0
  114. package/dist/onboarding.js +1075 -0
  115. package/dist/oversight.d.ts +264 -0
  116. package/dist/oversight.d.ts.map +1 -0
  117. package/dist/oversight.js +497 -0
  118. package/dist/presets.js +7 -7
  119. package/dist/quotas.d.ts +171 -0
  120. package/dist/quotas.d.ts.map +1 -0
  121. package/dist/quotas.js +259 -0
  122. package/dist/register.d.ts +13 -0
  123. package/dist/register.d.ts.map +1 -0
  124. package/dist/register.js +99 -0
  125. package/dist/registry.d.ts +1 -0
  126. package/dist/registry.d.ts.map +1 -1
  127. package/dist/registry.js +7 -0
  128. package/dist/registryData.json +43 -6
  129. package/dist/report.d.ts +2 -1
  130. package/dist/report.d.ts.map +1 -1
  131. package/dist/report.js +189 -2
  132. package/dist/reporting.d.ts +125 -0
  133. package/dist/reporting.d.ts.map +1 -1
  134. package/dist/reporting.js +192 -2
  135. package/dist/resources.d.ts +285 -0
  136. package/dist/resources.d.ts.map +1 -0
  137. package/dist/resources.js +643 -0
  138. package/dist/risk.d.ts +120 -0
  139. package/dist/risk.d.ts.map +1 -0
  140. package/dist/risk.js +220 -0
  141. package/dist/runtime.d.ts +74 -0
  142. package/dist/runtime.d.ts.map +1 -1
  143. package/dist/runtime.js +416 -18
  144. package/dist/schemaInference.d.ts +92 -0
  145. package/dist/schemaInference.d.ts.map +1 -0
  146. package/dist/schemaInference.js +466 -0
  147. package/dist/schema_validation.js +2 -2
  148. package/dist/schemas/config.schema.json +118 -4
  149. package/dist/security_report.js +4 -4
  150. package/dist/signing.d.ts +1 -1
  151. package/dist/signing.d.ts.map +1 -1
  152. package/dist/signing.js +4 -0
  153. package/dist/sinks/file.d.ts +19 -1
  154. package/dist/sinks/file.d.ts.map +1 -1
  155. package/dist/sinks/file.js +82 -13
  156. package/dist/sinks/https.d.ts +10 -0
  157. package/dist/sinks/https.d.ts.map +1 -1
  158. package/dist/sinks/https.js +76 -16
  159. package/dist/sinks/stdout.d.ts +1 -0
  160. package/dist/sinks/stdout.d.ts.map +1 -1
  161. package/dist/sinks/stdout.js +12 -1
  162. package/dist/spec.d.ts +159 -0
  163. package/dist/spec.d.ts.map +1 -0
  164. package/dist/spec.js +391 -0
  165. package/dist/stakeholders.d.ts +199 -0
  166. package/dist/stakeholders.d.ts.map +1 -0
  167. package/dist/stakeholders.js +398 -0
  168. package/dist/standards.d.ts.map +1 -1
  169. package/dist/standards.js +160 -2
  170. package/dist/standards_ingest.d.ts.map +1 -1
  171. package/dist/standards_ingest.js +1 -4
  172. package/dist/telemetry.d.ts +16 -2
  173. package/dist/telemetry.d.ts.map +1 -1
  174. package/dist/telemetry.js +77 -14
  175. package/dist/templates/controls/gdpr_control_catalog.json +261 -0
  176. package/dist/templates/controls/iso42001_control_catalog.json +1443 -0
  177. package/dist/templates/controls/soc2_control_catalog.json +163 -0
  178. package/dist/templates/standards/iso42001_claims.json +72 -0
  179. package/dist/traced_emitter.d.ts.map +1 -1
  180. package/dist/traced_emitter.js +19 -9
  181. package/dist/trust_package.d.ts +20 -1
  182. package/dist/trust_package.d.ts.map +1 -1
  183. package/dist/trust_package.js +90 -2
  184. package/dist/verify.d.ts.map +1 -1
  185. package/dist/verify.js +9 -2
  186. package/dist/wal.d.ts.map +1 -1
  187. package/dist/wal.js +2 -1
  188. package/package.json +14 -1
  189. package/scripts/postinstall.js +105 -210
  190. package/templates/controls/gdpr_control_catalog.json +261 -0
  191. package/templates/controls/iso42001_control_catalog.json +1443 -0
  192. package/templates/controls/soc2_control_catalog.json +163 -0
  193. package/templates/standards/iso42001_claims.json +72 -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,23 @@ 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
+ controlCatalogStandard: options?.controlCatalogStandard,
580
+ controlWorkflowState: options?.controlWorkflowState,
581
+ controlWorkflowStatePath: options?.controlWorkflowStatePath,
582
+ controlCoverageTarget: options?.controlCoverageTarget,
583
+ controlCoveragePath: options?.controlCoveragePath,
584
+ });
460
585
  if (options?.sign || options?.gpgKey || options?.gpgHome) {
461
586
  trustPackage = (0, trust_package_1.applyGpgSignature)(trustPackage, {
462
587
  gpgKey: options?.gpgKey,
@@ -549,6 +674,13 @@ function ensureState() {
549
674
  }
550
675
  return state;
551
676
  }
677
+ /**
678
+ * Get the current Monora state, or null if not initialized.
679
+ * Use this for optional event emission where initialization isn't required.
680
+ */
681
+ function getState() {
682
+ return state;
683
+ }
552
684
  function isRecoveredEventValid(event, config) {
553
685
  if (!event || typeof event !== 'object') {
554
686
  return false;
@@ -640,7 +772,7 @@ function emitEvent(event, options) {
640
772
  }
641
773
  return;
642
774
  }
643
- if ((0, context_1.getCurrentSpan)() || recovered) {
775
+ if ((0, context_1.getCurrentSpan)()) {
644
776
  try {
645
777
  current.reportManager.recordEvent(event);
646
778
  }
@@ -650,6 +782,38 @@ function emitEvent(event, options) {
650
782
  }
651
783
  current.dispatcher.emit(event);
652
784
  }
785
+ function emitInternal(payload, body, options) {
786
+ const current = getState();
787
+ if (!current) {
788
+ return null;
789
+ }
790
+ const raw = typeof payload === 'object' && payload ? payload : {};
791
+ const eventType = (typeof payload === 'string' ? payload : undefined) ||
792
+ raw.event_type ||
793
+ 'custom';
794
+ const eventBody = body !== undefined ? body : (raw.body || {});
795
+ const normalizedBody = eventBody && typeof eventBody === 'object' && !Array.isArray(eventBody)
796
+ ? eventBody
797
+ : { value: eventBody };
798
+ const built = current.eventBuilder.build(eventType, normalizedBody, {
799
+ dataClassification: options?.dataClassification ||
800
+ raw.data_classification ||
801
+ current.config.defaults?.data_classification ||
802
+ 'internal',
803
+ purpose: options?.purpose ||
804
+ raw.purpose ||
805
+ current.config.defaults?.purpose ||
806
+ 'governance',
807
+ reason: options?.reason || raw.reason || undefined,
808
+ parentEventId: options?.parentEventId || raw.parent_event_id || undefined,
809
+ });
810
+ const traceId = options?.traceId || raw.trace_id;
811
+ if (traceId) {
812
+ built.trace_id = traceId;
813
+ }
814
+ emitEvent(built);
815
+ return built;
816
+ }
653
817
  /**
654
818
  * Verify event hash in real-time as it's emitted.
655
819
  *
@@ -657,8 +821,10 @@ function emitEvent(event, options) {
657
821
  * Failures are logged as warnings (user decision: don't block).
658
822
  */
659
823
  function verifyEventRealTime(event, expectedPrevHash) {
660
- // Lazy import to avoid circular dependency
661
- const { verifyEventHash } = require('./verify');
824
+ const verifyEventHash = getVerifyEventHash();
825
+ if (!verifyEventHash) {
826
+ return;
827
+ }
662
828
  try {
663
829
  if (!verifyEventHash(event)) {
664
830
  logger_1.logger.error('Real-time verification FAILED: event %s has invalid hash', event.event_id);
@@ -673,8 +839,8 @@ function verifyEventRealTime(event, expectedPrevHash) {
673
839
  }
674
840
  function notifyViolation(violation) {
675
841
  const current = ensureState();
676
- // Record violation metric
677
- const policyType = violation.policyName || 'model_policy';
842
+ // Record violation metric (policyType matches Python SDK's policy_type attribute)
843
+ const policyType = violation.policyType || 'model_policy';
678
844
  const model = violation.model || 'unknown';
679
845
  (0, telemetry_1.recordViolation)(policyType, model);
680
846
  if (!current.violationHandler) {
@@ -687,14 +853,43 @@ function notifyViolation(violation) {
687
853
  logger_1.logger.error('Violation handler error: %s', error);
688
854
  }
689
855
  }
690
- function handleTraceCompletion(traceId) {
856
+ function emitSpanEvent(current, span, eventType, durationMs) {
857
+ const tracingConfig = current.config.tracing || {};
858
+ if (tracingConfig.emit_span_events === false) {
859
+ return;
860
+ }
861
+ const body = {
862
+ name: span.name,
863
+ metadata: span.metadata,
864
+ };
865
+ if (durationMs !== undefined) {
866
+ body.duration_ms = durationMs;
867
+ }
868
+ const event = current.eventBuilder.build(eventType, body);
869
+ emitEvent(event);
870
+ }
871
+ function handleTraceStart(span) {
872
+ const current = state;
873
+ if (!current) {
874
+ return;
875
+ }
876
+ emitSpanEvent(current, span, 'span_start');
877
+ }
878
+ function handleTraceCompletion(span) {
691
879
  const current = state;
692
- if (!current || !current.reportManager.isEnabled()) {
880
+ if (!current) {
881
+ return;
882
+ }
883
+ const traceStart = (0, context_1.getTraceStartTime)(span.traceId);
884
+ const durationMs = traceStart !== undefined ? Math.max(0, Date.now() - traceStart) : undefined;
885
+ emitSpanEvent(current, span, 'span_end', durationMs);
886
+ current.quotaManager.removeTrace(span.traceId);
887
+ if (!current.reportManager.isEnabled()) {
693
888
  return;
694
889
  }
695
890
  try {
696
891
  const registryMetadata = buildRegistryMetadata(current.registry);
697
- const result = current.reportManager.finalizeTrace(traceId, registryMetadata);
892
+ const result = current.reportManager.finalizeTrace(span.traceId, registryMetadata);
698
893
  if (!result) {
699
894
  return;
700
895
  }
@@ -755,6 +950,8 @@ function executeCall(current, fn, args, config, context) {
755
950
  const purpose = options.purpose || current.config.defaults?.purpose || 'general';
756
951
  const reason = options.reason;
757
952
  const resolvedModel = options.model || resolveModelFromArgs(args, current.registry);
953
+ const span = (0, context_1.getCurrentSpan)();
954
+ const traceId = span ? span.traceId : null;
758
955
  if (config.eventType === 'tool_call' && options.toolName) {
759
956
  try {
760
957
  const violation = current.toolPolicyEngine.checkTool(options.toolName);
@@ -870,6 +1067,9 @@ function executeCall(current, fn, args, config, context) {
870
1067
  throw violation;
871
1068
  }
872
1069
  }
1070
+ if (config.eventType === 'llm_call') {
1071
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason);
1072
+ }
873
1073
  const startTime = perf_hooks_1.performance.now();
874
1074
  const stepNumber = config.eventType === 'agent_step' ? (0, context_1.nextStepNumber)() : undefined;
875
1075
  try {
@@ -899,9 +1099,17 @@ function executeCall(current, fn, args, config, context) {
899
1099
  reason,
900
1100
  });
901
1101
  emitEvent(event);
1102
+ if (config.eventType === 'llm_call') {
1103
+ const { tokens, usage } = extractUsageMetrics(response);
1104
+ const cost = estimateUsageCost(current, resolvedModel, usage, tokens);
1105
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, tokens || [0, 0], cost ?? undefined);
1106
+ }
902
1107
  return response;
903
1108
  })
904
1109
  .catch((error) => {
1110
+ if (error instanceof quotas_1.QuotaExceededError) {
1111
+ throw error;
1112
+ }
905
1113
  const durationMs = perf_hooks_1.performance.now() - startTime;
906
1114
  const logErrors = current.config.error_handling?.log_user_exceptions !== false;
907
1115
  const body = config.buildBody({
@@ -925,6 +1133,9 @@ function executeCall(current, fn, args, config, context) {
925
1133
  reason,
926
1134
  });
927
1135
  emitEvent(event);
1136
+ if (config.eventType === 'llm_call') {
1137
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, [0, 0], undefined, false);
1138
+ }
928
1139
  throw error;
929
1140
  });
930
1141
  }
@@ -950,10 +1161,17 @@ function executeCall(current, fn, args, config, context) {
950
1161
  reason,
951
1162
  });
952
1163
  emitEvent(event);
1164
+ if (config.eventType === 'llm_call') {
1165
+ const { tokens, usage } = extractUsageMetrics(result);
1166
+ const cost = estimateUsageCost(current, resolvedModel, usage, tokens);
1167
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, tokens || [0, 0], cost ?? undefined);
1168
+ }
953
1169
  return result;
954
1170
  }
955
1171
  catch (error) {
956
- if (error instanceof data_handling_1.DataHandlingViolation || error instanceof policy_1.PolicyViolation) {
1172
+ if (error instanceof data_handling_1.DataHandlingViolation ||
1173
+ error instanceof policy_1.PolicyViolation ||
1174
+ error instanceof quotas_1.QuotaExceededError) {
957
1175
  throw error;
958
1176
  }
959
1177
  const durationMs = perf_hooks_1.performance.now() - startTime;
@@ -979,6 +1197,9 @@ function executeCall(current, fn, args, config, context) {
979
1197
  reason,
980
1198
  });
981
1199
  emitEvent(event);
1200
+ if (config.eventType === 'llm_call') {
1201
+ applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, [0, 0], undefined, false);
1202
+ }
982
1203
  throw error;
983
1204
  }
984
1205
  }
@@ -1030,6 +1251,48 @@ function emitRegistryOverride(current, model, dataClassification, purpose, reaso
1030
1251
  emitEvent(event);
1031
1252
  return event.event_id || null;
1032
1253
  }
1254
+ function applyQuotaLimits(current, traceId, model, dataClassification, purpose, reason, tokens, cost, raiseOnBlock = true) {
1255
+ if (!traceId || !current.quotaManager.isEnabled()) {
1256
+ return;
1257
+ }
1258
+ const tracker = current.quotaManager.getTracker(traceId);
1259
+ if (tokens || cost !== undefined) {
1260
+ const [promptTokens, completionTokens] = tokens || [0, 0];
1261
+ tracker.recordTokenUsage(promptTokens, completionTokens, cost);
1262
+ }
1263
+ const result = tracker.checkQuotas();
1264
+ if (result.allowed) {
1265
+ return;
1266
+ }
1267
+ emitQuotaViolation(current.eventBuilder, model, result, dataClassification, purpose, reason);
1268
+ const violation = new quotas_1.QuotaExceededError(result.exceededQuota || 'unknown', result.currentValue || 0, result.limitValue || 0, model);
1269
+ notifyViolation(violation);
1270
+ const action = tracker.getConfig().action;
1271
+ if (action === 'block' && raiseOnBlock) {
1272
+ throw violation;
1273
+ }
1274
+ logger_1.logger.warning('Quota warning: %s', result.message);
1275
+ }
1276
+ function emitQuotaViolation(builder, model, result, dataClassification, purpose, reason) {
1277
+ const body = {
1278
+ model,
1279
+ policy_name: `quota.${result.exceededQuota || 'unknown'}`,
1280
+ message: result.message,
1281
+ status: 'policy_violation',
1282
+ error: null,
1283
+ quota: {
1284
+ type: result.exceededQuota ?? null,
1285
+ current_value: result.currentValue ?? null,
1286
+ limit_value: result.limitValue ?? null,
1287
+ },
1288
+ };
1289
+ const event = builder.build('llm_call', body, {
1290
+ dataClassification,
1291
+ purpose,
1292
+ reason,
1293
+ });
1294
+ emitEvent(event);
1295
+ }
1033
1296
  function buildLlmBody(params) {
1034
1297
  const responseDetails = params.response ? extractResponseDetails(params.response) : null;
1035
1298
  let explanation;
@@ -1101,6 +1364,132 @@ function extractResponseDetails(response) {
1101
1364
  }
1102
1365
  return { content, finish_reason: finishReason, usage };
1103
1366
  }
1367
+ function extractUsageMetrics(response) {
1368
+ if (!response) {
1369
+ return { tokens: null, usage: null };
1370
+ }
1371
+ const details = extractResponseDetails(response);
1372
+ const usage = details && typeof details === 'object' ? details.usage : null;
1373
+ return { tokens: normalizeTokenUsage(usage), usage };
1374
+ }
1375
+ function estimateUsageCost(current, model, usage, tokens) {
1376
+ if (!usage || typeof usage !== 'object') {
1377
+ return null;
1378
+ }
1379
+ for (const key of ['total_cost', 'cost', 'estimated_cost', 'total_cost_usd', 'cost_usd']) {
1380
+ const value = usage[key];
1381
+ const cost = coerceCost(value);
1382
+ if (cost !== null) {
1383
+ return cost;
1384
+ }
1385
+ }
1386
+ if (!model || !tokens) {
1387
+ return null;
1388
+ }
1389
+ const metadata = current.registry.getModelMetadata(model);
1390
+ const pricing = metadata && typeof metadata === 'object' ? metadata.pricing : null;
1391
+ return estimateCostFromPricing(pricing, tokens);
1392
+ }
1393
+ function estimateCostFromPricing(pricing, tokens) {
1394
+ if (!pricing || typeof pricing !== 'object') {
1395
+ return null;
1396
+ }
1397
+ const [promptTokens, completionTokens] = tokens;
1398
+ const pickRatePerToken = (candidates) => {
1399
+ for (const candidate of candidates) {
1400
+ if (!(candidate.key in pricing)) {
1401
+ continue;
1402
+ }
1403
+ const cost = coerceCost(pricing[candidate.key]);
1404
+ if (cost === null) {
1405
+ continue;
1406
+ }
1407
+ return cost / candidate.scale;
1408
+ }
1409
+ return null;
1410
+ };
1411
+ const totalRate = pickRatePerToken([
1412
+ { key: 'cost_per_token', scale: 1 },
1413
+ { key: 'cost_per_1k_tokens', scale: 1000 },
1414
+ { key: 'total_cost_per_1k', scale: 1000 },
1415
+ { key: 'cost_per_1m_tokens', scale: 1000000 },
1416
+ { key: 'total_cost_per_1m', scale: 1000000 },
1417
+ ]);
1418
+ const promptRate = pickRatePerToken([
1419
+ { key: 'prompt_cost_per_token', scale: 1 },
1420
+ { key: 'input_cost_per_token', scale: 1 },
1421
+ { key: 'prompt_cost_per_1k', scale: 1000 },
1422
+ { key: 'input_cost_per_1k', scale: 1000 },
1423
+ { key: 'prompt_cost_per_1m', scale: 1000000 },
1424
+ { key: 'input_cost_per_1m', scale: 1000000 },
1425
+ ]);
1426
+ const completionRate = pickRatePerToken([
1427
+ { key: 'completion_cost_per_token', scale: 1 },
1428
+ { key: 'output_cost_per_token', scale: 1 },
1429
+ { key: 'completion_cost_per_1k', scale: 1000 },
1430
+ { key: 'output_cost_per_1k', scale: 1000 },
1431
+ { key: 'completion_cost_per_1m', scale: 1000000 },
1432
+ { key: 'output_cost_per_1m', scale: 1000000 },
1433
+ ]);
1434
+ if (totalRate !== null) {
1435
+ return (promptTokens + completionTokens) * totalRate;
1436
+ }
1437
+ if (promptRate === null && completionRate === null) {
1438
+ return null;
1439
+ }
1440
+ let cost = 0;
1441
+ if (promptRate !== null) {
1442
+ cost += promptTokens * promptRate;
1443
+ }
1444
+ if (completionRate !== null) {
1445
+ cost += completionTokens * completionRate;
1446
+ }
1447
+ return cost;
1448
+ }
1449
+ function normalizeTokenUsage(usage) {
1450
+ if (!usage || typeof usage !== 'object') {
1451
+ return null;
1452
+ }
1453
+ let prompt = coerceToken(usage.prompt_tokens);
1454
+ let completion = coerceToken(usage.completion_tokens);
1455
+ if (prompt === null) {
1456
+ prompt = coerceToken(usage.input_tokens);
1457
+ }
1458
+ if (completion === null) {
1459
+ completion = coerceToken(usage.output_tokens);
1460
+ }
1461
+ const total = coerceToken(usage.total_tokens);
1462
+ if (prompt === null && completion === null && total === null) {
1463
+ return null;
1464
+ }
1465
+ if (prompt === null) {
1466
+ prompt = Math.max(0, (total || 0) - (completion || 0));
1467
+ }
1468
+ if (completion === null) {
1469
+ completion = Math.max(0, (total || 0) - (prompt || 0));
1470
+ }
1471
+ return [prompt || 0, completion || 0];
1472
+ }
1473
+ function coerceToken(value) {
1474
+ if (value === null || value === undefined) {
1475
+ return null;
1476
+ }
1477
+ const number = Number(value);
1478
+ if (!Number.isFinite(number)) {
1479
+ return null;
1480
+ }
1481
+ return Math.trunc(number);
1482
+ }
1483
+ function coerceCost(value) {
1484
+ if (value === null || value === undefined) {
1485
+ return null;
1486
+ }
1487
+ const number = Number(value);
1488
+ if (!Number.isFinite(number)) {
1489
+ return null;
1490
+ }
1491
+ return number;
1492
+ }
1104
1493
  function formatError(error) {
1105
1494
  return { type: error.name || 'Error', message: error.message || String(error) };
1106
1495
  }
@@ -1180,7 +1569,7 @@ function buildSinks(sinkConfigs, failFast) {
1180
1569
  const sinkType = String(config.type || '').toLowerCase();
1181
1570
  try {
1182
1571
  if (sinkType === 'stdout') {
1183
- sinks.push(new sinks_1.StdoutSink(config.format || 'json'));
1572
+ sinks.push(new sinks_1.StdoutSink(config.format || 'pretty'));
1184
1573
  }
1185
1574
  else if (sinkType === 'file') {
1186
1575
  sinks.push(new sinks_1.FileSink(config.path, {
@@ -1188,6 +1577,7 @@ function buildSinks(sinkConfigs, failFast) {
1188
1577
  flushIntervalSec: config.flush_interval_sec || 5.0,
1189
1578
  rotation: config.rotation || 'none',
1190
1579
  maxSizeMb: config.max_size_mb,
1580
+ symlink: config.symlink,
1191
1581
  }));
1192
1582
  }
1193
1583
  else if (sinkType === 'https' || sinkType === 'http') {
@@ -1206,6 +1596,12 @@ function buildSinks(sinkConfigs, failFast) {
1206
1596
  headerName: config.idempotency.header_name,
1207
1597
  }
1208
1598
  : undefined;
1599
+ const dataResidencyConfig = config.data_residency
1600
+ ? {
1601
+ residency: config.data_residency,
1602
+ action: config.data_residency_action,
1603
+ }
1604
+ : undefined;
1209
1605
  sinks.push(new sinks_1.HttpSink(config.endpoint, headers, {
1210
1606
  batchSize: config.batch_size || 50,
1211
1607
  timeoutSec: config.timeout_sec || 10.0,
@@ -1214,6 +1610,7 @@ function buildSinks(sinkConfigs, failFast) {
1214
1610
  circuitBreaker: config.circuit_breaker,
1215
1611
  retryQueue: retryQueueConfig,
1216
1612
  idempotency: idempotencyConfig,
1613
+ dataResidency: dataResidencyConfig,
1217
1614
  }));
1218
1615
  }
1219
1616
  else if (sinkType) {
@@ -1252,5 +1649,6 @@ function findEventSource(config) {
1252
1649
  return String(sink.path);
1253
1650
  }
1254
1651
  }
1255
- return config.error_handling?.fallback_path;
1652
+ const fallback = config.error_handling?.fallback_path;
1653
+ return fallback ? String(fallback) : undefined;
1256
1654
  }