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.
- package/README.md +441 -150
- package/dist/aims_governance.d.ts +238 -0
- package/dist/aims_governance.d.ts.map +1 -0
- package/dist/aims_governance.js +922 -0
- package/dist/alerts.d.ts +16 -0
- package/dist/alerts.d.ts.map +1 -1
- package/dist/alerts.js +16 -0
- package/dist/api.d.ts +6 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -0
- package/dist/assessment.d.ts +269 -0
- package/dist/assessment.d.ts.map +1 -0
- package/dist/assessment.js +1232 -0
- package/dist/attestation.js +23 -1
- package/dist/attribution.d.ts +349 -0
- package/dist/attribution.d.ts.map +1 -0
- package/dist/attribution.js +987 -0
- package/dist/autodetect.d.ts +69 -1
- package/dist/autodetect.d.ts.map +1 -1
- package/dist/autodetect.js +644 -1
- package/dist/bias.d.ts +130 -0
- package/dist/bias.d.ts.map +1 -0
- package/dist/bias.js +223 -0
- package/dist/circuit_breaker.js +3 -3
- package/dist/cli/diagnostics.d.ts +5 -1
- package/dist/cli/diagnostics.d.ts.map +1 -1
- package/dist/cli/diagnostics.js +31 -8
- package/dist/cli/doctor.d.ts +25 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +381 -0
- package/dist/cli/fix.d.ts +16 -0
- package/dist/cli/fix.d.ts.map +1 -0
- package/dist/cli/fix.js +284 -0
- package/dist/cli/init.d.ts +57 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +205 -0
- package/dist/cli.js +1611 -126
- package/dist/complianceTargets.d.ts +111 -0
- package/dist/complianceTargets.d.ts.map +1 -0
- package/dist/complianceTargets.js +521 -0
- package/dist/config.d.ts +301 -17
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +428 -36
- package/dist/config_migrations.d.ts +41 -0
- package/dist/config_migrations.d.ts.map +1 -1
- package/dist/config_migrations.js +205 -0
- package/dist/config_schema.d.ts +2900 -731
- package/dist/config_schema.d.ts.map +1 -1
- package/dist/config_schema.js +257 -55
- package/dist/context.d.ts +34 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +118 -7
- package/dist/control_backbone.d.ts +122 -0
- package/dist/control_backbone.d.ts.map +1 -0
- package/dist/control_backbone.js +698 -0
- package/dist/data-governance.d.ts +187 -0
- package/dist/data-governance.d.ts.map +1 -0
- package/dist/data-governance.js +424 -0
- package/dist/dataResidency.d.ts +44 -0
- package/dist/dataResidency.d.ts.map +1 -0
- package/dist/dataResidency.js +203 -0
- package/dist/dispatcher.d.ts +32 -0
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/dispatcher.js +91 -4
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +38 -0
- package/dist/evidence_store.d.ts +103 -0
- package/dist/evidence_store.d.ts.map +1 -0
- package/dist/evidence_store.js +459 -0
- package/dist/executiveSummary.d.ts +65 -8
- package/dist/executiveSummary.d.ts.map +1 -1
- package/dist/executiveSummary.js +289 -26
- package/dist/identity.d.ts +143 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +231 -0
- package/dist/impact-assessment.d.ts +350 -0
- package/dist/impact-assessment.d.ts.map +1 -0
- package/dist/impact-assessment.js +580 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +300 -4
- package/dist/instrumentation.d.ts +1 -1
- package/dist/instrumentation.d.ts.map +1 -1
- package/dist/instrumentation.js +243 -27
- package/dist/integrations/anthropic.d.ts +3 -0
- package/dist/integrations/anthropic.d.ts.map +1 -1
- package/dist/integrations/anthropic.js +284 -79
- package/dist/integrations/governance.d.ts +33 -0
- package/dist/integrations/governance.d.ts.map +1 -0
- package/dist/integrations/governance.js +208 -0
- package/dist/integrations/langchain.d.ts +7 -0
- package/dist/integrations/langchain.d.ts.map +1 -1
- package/dist/integrations/langchain.js +387 -143
- package/dist/integrations/openai.d.ts +9 -0
- package/dist/integrations/openai.d.ts.map +1 -1
- package/dist/integrations/openai.js +673 -73
- package/dist/iso42001_consolidation.d.ts +16 -0
- package/dist/iso42001_consolidation.d.ts.map +1 -0
- package/dist/iso42001_consolidation.js +413 -0
- package/dist/iso42001_workflows.d.ts +263 -0
- package/dist/iso42001_workflows.d.ts.map +1 -0
- package/dist/iso42001_workflows.js +781 -0
- package/dist/lifecycle.d.ts +299 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +624 -0
- package/dist/lineage.d.ts +2 -2
- package/dist/lineage.d.ts.map +1 -1
- package/dist/lineage.js +12 -17
- package/dist/middleware/express.d.ts.map +1 -1
- package/dist/middleware/express.js +33 -3
- package/dist/middleware/nextjs.d.ts.map +1 -1
- package/dist/middleware/nextjs.js +42 -68
- package/dist/model.d.ts +143 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +371 -0
- package/dist/onboarding.d.ts +42 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +1022 -0
- package/dist/oversight.d.ts +264 -0
- package/dist/oversight.d.ts.map +1 -0
- package/dist/oversight.js +497 -0
- package/dist/pdf_report.d.ts.map +1 -1
- package/dist/pdf_report.js +42 -21
- package/dist/presets.d.ts +88 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +520 -0
- package/dist/propagation.d.ts.map +1 -1
- package/dist/propagation.js +34 -2
- package/dist/quotas.d.ts +171 -0
- package/dist/quotas.d.ts.map +1 -0
- package/dist/quotas.js +259 -0
- package/dist/register.d.ts +13 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +99 -0
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +7 -0
- package/dist/registryData.json +43 -6
- package/dist/report.d.ts +2 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +189 -2
- package/dist/reporting.d.ts +125 -0
- package/dist/reporting.d.ts.map +1 -1
- package/dist/reporting.js +196 -5
- package/dist/resources.d.ts +285 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +643 -0
- package/dist/risk.d.ts +120 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +220 -0
- package/dist/runtime.d.ts +74 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +598 -22
- package/dist/schemaInference.d.ts +92 -0
- package/dist/schemaInference.d.ts.map +1 -0
- package/dist/schemaInference.js +466 -0
- package/dist/schema_validation.js +2 -2
- package/dist/schemas/config.schema.json +169 -6
- package/dist/schemas/event.schema.json +4 -0
- package/dist/security_report.js +4 -4
- package/dist/signing.d.ts +1 -1
- package/dist/signing.d.ts.map +1 -1
- package/dist/signing.js +4 -0
- package/dist/sinks/file.d.ts +19 -1
- package/dist/sinks/file.d.ts.map +1 -1
- package/dist/sinks/file.js +82 -13
- package/dist/sinks/https.d.ts +10 -0
- package/dist/sinks/https.d.ts.map +1 -1
- package/dist/sinks/https.js +76 -16
- package/dist/sinks/stdout.d.ts +1 -0
- package/dist/sinks/stdout.d.ts.map +1 -1
- package/dist/sinks/stdout.js +12 -1
- package/dist/spec.d.ts +159 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +391 -0
- package/dist/stakeholders.d.ts +199 -0
- package/dist/stakeholders.d.ts.map +1 -0
- package/dist/stakeholders.js +398 -0
- package/dist/standards.d.ts.map +1 -1
- package/dist/standards.js +160 -2
- package/dist/standards_ingest.d.ts +2 -2
- package/dist/standards_ingest.d.ts.map +1 -1
- package/dist/standards_ingest.js +105 -23
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +7 -2
- package/dist/telemetry.d.ts +16 -2
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +79 -14
- package/dist/templates/controls/iso42001_control_catalog.json +1443 -0
- package/dist/traced_emitter.d.ts +3 -0
- package/dist/traced_emitter.d.ts.map +1 -1
- package/dist/traced_emitter.js +142 -25
- package/dist/trust_package.d.ts +21 -1
- package/dist/trust_package.d.ts.map +1 -1
- package/dist/trust_package.js +101 -4
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +9 -2
- package/dist/wal.d.ts.map +1 -1
- package/dist/wal.js +2 -1
- package/package.json +14 -1
- package/scripts/postinstall.js +119 -97
- 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
|
-
|
|
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
|
|
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)()
|
|
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
|
-
|
|
482
|
-
|
|
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.
|
|
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
|
|
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
|
|
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 ||
|
|
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 || '
|
|
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
|
-
|
|
1651
|
+
const fallback = config.error_handling?.fallback_path;
|
|
1652
|
+
return fallback ? String(fallback) : undefined;
|
|
1077
1653
|
}
|