monora-ai 1.6.0 → 1.7.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAIxC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAelE;AAmDD,wBAAgB,WAAW,CACzB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAClC,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CA2LrB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAEzE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAsD7E"}
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAIxC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAelE;AAqJD,wBAAgB,WAAW,CACzB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAClC,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAkMrB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAEzE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAsD7E"}
package/dist/report.js CHANGED
@@ -78,6 +78,99 @@ function coerceTokenCount(value) {
78
78
  const numberValue = Number(value);
79
79
  return Number.isFinite(numberValue) ? numberValue : null;
80
80
  }
81
+ function resolvePolicyName(body) {
82
+ for (const key of ['policy_name', 'policy', 'policy_id', 'policyId']) {
83
+ const value = body[key];
84
+ if (value) {
85
+ const candidate = String(value).trim();
86
+ if (candidate) {
87
+ return candidate;
88
+ }
89
+ }
90
+ }
91
+ if (body.policy && typeof body.policy === 'object') {
92
+ for (const key of ['name', 'id', 'policy_name']) {
93
+ const value = body.policy[key];
94
+ if (value) {
95
+ const candidate = String(value).trim();
96
+ if (candidate) {
97
+ return candidate;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ if (body.data_handling && typeof body.data_handling === 'object' && body.data_handling.action) {
103
+ return 'data_handling.block';
104
+ }
105
+ return 'UNKNOWN_POLICY';
106
+ }
107
+ function resolvePolicyMessage(body) {
108
+ if (body.message) {
109
+ const candidate = String(body.message).trim();
110
+ if (candidate) {
111
+ return candidate;
112
+ }
113
+ }
114
+ if (body.data_handling && typeof body.data_handling === 'object') {
115
+ const rules = body.data_handling.rules;
116
+ if (Array.isArray(rules) && rules.length > 0) {
117
+ return `Sensitive data matched rules: ${rules.map(String).join(', ')}`;
118
+ }
119
+ if (body.data_handling.action) {
120
+ return `Data handling policy violation (${body.data_handling.action})`;
121
+ }
122
+ }
123
+ return 'Policy violation recorded';
124
+ }
125
+ function normalizeUsage(usage) {
126
+ if (!usage || typeof usage !== 'object') {
127
+ return null;
128
+ }
129
+ let prompt = coerceTokenCount(usage.prompt_tokens);
130
+ let completion = coerceTokenCount(usage.completion_tokens);
131
+ if (prompt === null) {
132
+ prompt = coerceTokenCount(usage.input_tokens);
133
+ }
134
+ if (completion === null) {
135
+ completion = coerceTokenCount(usage.output_tokens);
136
+ }
137
+ const total = coerceTokenCount(usage.total_tokens);
138
+ if (prompt === null && completion === null && total === null) {
139
+ return null;
140
+ }
141
+ const safePrompt = prompt ?? 0;
142
+ const safeCompletion = completion ?? 0;
143
+ const safeTotal = total ?? safePrompt + safeCompletion;
144
+ return { prompt: safePrompt, completion: safeCompletion, total: safeTotal };
145
+ }
146
+ function extractUsage(body) {
147
+ const response = body.response;
148
+ if (response && typeof response === 'object') {
149
+ const normalized = normalizeUsage(response.usage);
150
+ if (normalized) {
151
+ return normalized;
152
+ }
153
+ }
154
+ const usage = normalizeUsage(body.usage);
155
+ if (usage) {
156
+ return usage;
157
+ }
158
+ const tokenUsage = normalizeUsage(body.token_usage);
159
+ if (tokenUsage) {
160
+ return tokenUsage;
161
+ }
162
+ return null;
163
+ }
164
+ function classifyPolicyViolation(policyName) {
165
+ const lowered = policyName.toLowerCase();
166
+ if (lowered.includes('unknown_model')) {
167
+ return 'unknown';
168
+ }
169
+ if (['denylist', 'blocklist', 'forbidden'].some((token) => lowered.includes(token))) {
170
+ return 'forbidden';
171
+ }
172
+ return null;
173
+ }
81
174
  function compilePatterns(patterns) {
82
175
  return patterns.map((pattern) => {
83
176
  const regexPattern = pattern
@@ -123,6 +216,31 @@ function buildReport(events, policies) {
123
216
  const usedAllowlistPatterns = new Set();
124
217
  const missingUsageModels = new Set();
125
218
  let missingUsageEvents = 0;
219
+ for (const event of filteredEvents) {
220
+ const body = event && typeof event.body === 'object' && event.body
221
+ ? event.body
222
+ : {};
223
+ if (body.status === 'policy_violation') {
224
+ const policyName = resolvePolicyName(body) || 'UNKNOWN_POLICY';
225
+ const message = resolvePolicyMessage(body) || 'Policy violation recorded';
226
+ violations.push({
227
+ timestamp: event.timestamp,
228
+ model: body.model,
229
+ policy: policyName,
230
+ message,
231
+ });
232
+ const model = normalizeModelName(body.model);
233
+ if (model) {
234
+ const classification = classifyPolicyViolation(policyName);
235
+ if (classification === 'forbidden') {
236
+ forbiddenModelsBlocked.add(model);
237
+ }
238
+ else if (classification === 'unknown') {
239
+ unknownModelsUsed.add(model);
240
+ }
241
+ }
242
+ }
243
+ }
126
244
  for (const event of filteredEvents) {
127
245
  if (event.trace_id) {
128
246
  traceIds.add(event.trace_id);
@@ -141,64 +259,39 @@ function buildReport(events, policies) {
141
259
  byClassification[event.data_classification] =
142
260
  (byClassification[event.data_classification] || 0) + 1;
143
261
  }
144
- const body = event.body || {};
262
+ const body = event && typeof event.body === 'object' && event.body
263
+ ? event.body
264
+ : {};
145
265
  if (event.event_type === 'llm_call') {
146
266
  const model = normalizeModelName(body.model);
147
- if (model) {
267
+ const isBlocked = body.status === 'policy_violation';
268
+ if (model && !isBlocked) {
148
269
  byModel[model] = (byModel[model] || 0) + 1;
149
270
  modelsUsed.add(model);
150
271
  }
151
- const response = body.response;
152
- if (response && typeof response === 'object') {
153
- const usage = response.usage;
154
- if (usage && typeof usage === 'object') {
155
- const prompt = coerceTokenCount(usage.prompt_tokens) ?? 0;
156
- const completion = coerceTokenCount(usage.completion_tokens) ?? 0;
157
- const total = coerceTokenCount(usage.total_tokens) ??
158
- (coerceTokenCount(usage.prompt_tokens) !== null ||
159
- coerceTokenCount(usage.completion_tokens) !== null
160
- ? prompt + completion
161
- : null);
162
- if (total === null && prompt === 0 && completion === 0) {
163
- missingUsageEvents += 1;
164
- if (model) {
165
- missingUsageModels.add(model);
166
- }
167
- }
168
- else {
169
- tokenUsage.total_prompt_tokens += prompt;
170
- tokenUsage.total_completion_tokens += completion;
171
- tokenUsage.total_tokens += total ?? 0;
172
- if (model) {
173
- if (!tokenUsage.by_model[model]) {
174
- tokenUsage.by_model[model] = { prompt: 0, completion: 0, total: 0 };
175
- }
176
- tokenUsage.by_model[model].prompt += prompt;
177
- tokenUsage.by_model[model].completion += completion;
178
- tokenUsage.by_model[model].total += total ?? 0;
179
- }
272
+ if (!isBlocked) {
273
+ const usage = extractUsage(body);
274
+ if (!usage) {
275
+ missingUsageEvents += 1;
276
+ if (model) {
277
+ missingUsageModels.add(model);
180
278
  }
181
279
  }
182
280
  else {
183
- missingUsageEvents += 1;
281
+ tokenUsage.total_prompt_tokens += usage.prompt;
282
+ tokenUsage.total_completion_tokens += usage.completion;
283
+ tokenUsage.total_tokens += usage.total;
184
284
  if (model) {
185
- missingUsageModels.add(model);
285
+ if (!tokenUsage.by_model[model]) {
286
+ tokenUsage.by_model[model] = { prompt: 0, completion: 0, total: 0 };
287
+ }
288
+ tokenUsage.by_model[model].prompt += usage.prompt;
289
+ tokenUsage.by_model[model].completion += usage.completion;
290
+ tokenUsage.by_model[model].total += usage.total;
186
291
  }
187
292
  }
188
293
  }
189
294
  }
190
- if (body.status === 'policy_violation') {
191
- violations.push({
192
- timestamp: event.timestamp,
193
- model: body.model,
194
- policy: body.policy_name,
195
- message: body.message,
196
- });
197
- const model = normalizeModelName(body.model);
198
- if (model) {
199
- forbiddenModelsBlocked.add(model);
200
- }
201
- }
202
295
  if (body.error) {
203
296
  errors.push({
204
297
  timestamp: event.timestamp,
@@ -238,6 +331,9 @@ function buildReport(events, policies) {
238
331
  allowedModelsUsed.add(model);
239
332
  }
240
333
  }
334
+ for (const model of modelsUsed) {
335
+ forbiddenModelsBlocked.delete(model);
336
+ }
241
337
  for (const model of allowedModelsUsed) {
242
338
  unknownModelsUsed.delete(model);
243
339
  }
@@ -255,6 +351,7 @@ function buildReport(events, policies) {
255
351
  start: timestamps.length ? new Date(Math.min(...timestamps.map((t) => t.getTime()))).toISOString() : null,
256
352
  end: timestamps.length ? new Date(Math.max(...timestamps.map((t) => t.getTime()))).toISOString() : null,
257
353
  };
354
+ const unknownModelsPayload = policies || unknownModelsUsed.size > 0 ? Array.from(unknownModelsUsed).sort() : [];
258
355
  return {
259
356
  total_events: filteredEvents.length,
260
357
  traces: traceIds.size,
@@ -269,7 +366,7 @@ function buildReport(events, policies) {
269
366
  model_compliance: {
270
367
  allowed_models_used: Array.from(allowedModelsUsed).sort(),
271
368
  forbidden_models_blocked: Array.from(forbiddenModelsBlocked).sort(),
272
- unknown_models_used: policies ? Array.from(unknownModelsUsed).sort() : [],
369
+ unknown_models_used: unknownModelsPayload,
273
370
  unused_allowlist_patterns: unusedAllowlistPatterns.sort(),
274
371
  },
275
372
  };
package/dist/reporting.js CHANGED
@@ -365,24 +365,67 @@ function verifyChainStatusWithProof(events, config) {
365
365
  return ['failed', exc instanceof Error ? exc.message : String(exc), null];
366
366
  }
367
367
  }
368
+ const INVALID_IDENTIFIER_VALUES = new Set(['__main__.py', '__main__', 'unknown']);
369
+ function sanitizeIdentifier(value) {
370
+ if (value === null || value === undefined) {
371
+ return null;
372
+ }
373
+ const candidate = String(value).trim();
374
+ if (!candidate) {
375
+ return null;
376
+ }
377
+ const base = candidate.split(/[\\/]/).pop() || '';
378
+ if (!base) {
379
+ return null;
380
+ }
381
+ if (INVALID_IDENTIFIER_VALUES.has(base.toLowerCase())) {
382
+ return null;
383
+ }
384
+ return base;
385
+ }
386
+ function normalizeEnvironmentValue(value) {
387
+ if (typeof value !== 'string') {
388
+ return 'dev';
389
+ }
390
+ const normalized = value.trim();
391
+ if (!normalized) {
392
+ return 'dev';
393
+ }
394
+ if (normalized.toLowerCase() === 'development') {
395
+ return 'dev';
396
+ }
397
+ return normalized;
398
+ }
368
399
  function resolveProjectName(config) {
369
- const projectName = process.env.MONORA_PROJECT_NAME ||
370
- process.env.PROJECT_NAME ||
371
- config?.defaults?.service_name ||
400
+ const projectName = sanitizeIdentifier(process.env.MONORA_PROJECT_NAME) ||
401
+ sanitizeIdentifier(process.env.PROJECT_NAME) ||
402
+ sanitizeIdentifier(config?.defaults?.service_name) ||
372
403
  'monora';
373
- if (!projectName || projectName === 'unknown') {
404
+ if (projectName === 'monora') {
374
405
  console.error('Monora: project name missing; defaulting to "monora"');
375
- return 'monora';
376
406
  }
377
407
  return projectName;
378
408
  }
379
409
  function resolveEnvironment(config) {
380
- return config?.defaults?.environment || process.env.MONORA_ENV || 'dev';
410
+ const rawEnv = config?.defaults?.environment || process.env.MONORA_ENV || 'dev';
411
+ return normalizeEnvironmentValue(rawEnv);
381
412
  }
382
413
  function shouldRedactHost(config) {
383
414
  const reporting = config?.reporting || {};
384
415
  return reporting.redact_host !== false;
385
416
  }
417
+ function applyTrustProofOverrides(events, environment) {
418
+ return events.map((event) => {
419
+ if (!event || typeof event !== 'object') {
420
+ return event;
421
+ }
422
+ const currentEnv = event.environment;
423
+ if (!currentEnv || normalizeEnvironmentValue(currentEnv) !== environment) {
424
+ return { ...event, environment };
425
+ }
426
+ return event;
427
+ });
428
+ }
386
429
  function redactEventHosts(events, prefix) {
387
430
  const mapping = new Map();
388
431
  return events.map((event) => {
@@ -405,7 +448,11 @@ function redactEventHosts(events, prefix) {
405
448
  function buildTrustProofBundle(traceId, report, events, chainProof, artifacts, config) {
406
449
  const redactHosts = shouldRedactHost(config);
407
450
  const redactionPrefix = 'host-sha256:';
408
- const sanitizedEvents = redactHosts ? redactEventHosts(events, redactionPrefix) : events;
451
+ const environment = resolveEnvironment(config);
452
+ const normalizedEvents = applyTrustProofOverrides(events, environment);
453
+ const sanitizedEvents = redactHosts
454
+ ? redactEventHosts(normalizedEvents, redactionPrefix)
455
+ : normalizedEvents;
409
456
  // Compute events digest
410
457
  const eventsDigestHash = (0, verify_1.computeEventsDigest)(sanitizedEvents);
411
458
  // Build artifact list for bundle
@@ -422,7 +469,6 @@ function buildTrustProofBundle(traceId, report, events, chainProof, artifacts, c
422
469
  }
423
470
  // Get project name from config
424
471
  const projectName = resolveProjectName(config);
425
- const environment = resolveEnvironment(config);
426
472
  // Build the bundle
427
473
  const reportBytes = (0, attestation_1.serializeReport)(report);
428
474
  let bundle = (0, attestation_1.buildAttestationBundle)(report, reportBytes, undefined, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monora-ai",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Lightweight governance and trace SDK for AI systems",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,10 +11,12 @@
11
11
  "build": "tsc && node scripts/copy-files.js",
12
12
  "test": "jest",
13
13
  "prepublishOnly": "npm run build",
14
+ "postinstall": "node scripts/postinstall.js",
14
15
  "clean": "rm -rf dist"
15
16
  },
16
17
  "files": [
17
18
  "dist",
19
+ "scripts",
18
20
  "README.md",
19
21
  "LICENSE"
20
22
  ],
@@ -0,0 +1,10 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const rootDir = path.resolve(__dirname, '..');
5
+ const srcPath = path.join(rootDir, 'src', 'registryData.json');
6
+ const distDir = path.join(rootDir, 'dist');
7
+ const destPath = path.join(distDir, 'registryData.json');
8
+
9
+ fs.mkdirSync(distDir, { recursive: true });
10
+ fs.copyFileSync(srcPath, destPath);
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall script for Monora SDK
4
+ * Automatically runs the setup wizard after npm install
5
+ */
6
+
7
+ const { execSync, spawn } = require('child_process');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ // Check if we're in a CI environment or non-interactive terminal
12
+ function isCI() {
13
+ return !!(
14
+ process.env.CI ||
15
+ process.env.CONTINUOUS_INTEGRATION ||
16
+ process.env.BUILD_NUMBER ||
17
+ process.env.GITHUB_ACTIONS ||
18
+ process.env.GITLAB_CI ||
19
+ process.env.CIRCLECI ||
20
+ process.env.TRAVIS ||
21
+ process.env.JENKINS_URL ||
22
+ process.env.MONORA_SKIP_POSTINSTALL
23
+ );
24
+ }
25
+
26
+ function isTTY() {
27
+ return process.stdout.isTTY && process.stdin.isTTY;
28
+ }
29
+
30
+ function findProjectRoot() {
31
+ // Walk up from current directory to find package.json (the user's project)
32
+ let dir = process.cwd();
33
+ const maxLevels = 10;
34
+
35
+ for (let i = 0; i < maxLevels; i++) {
36
+ const pkgPath = path.join(dir, 'package.json');
37
+ if (fs.existsSync(pkgPath)) {
38
+ try {
39
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
40
+ // Skip if this is the monora-ai package itself
41
+ if (pkg.name !== 'monora-ai') {
42
+ return dir;
43
+ }
44
+ } catch (e) {
45
+ // Continue searching
46
+ }
47
+ }
48
+ const parent = path.dirname(dir);
49
+ if (parent === dir) break;
50
+ dir = parent;
51
+ }
52
+ return process.cwd();
53
+ }
54
+
55
+ function configExists(projectRoot) {
56
+ const configFiles = [
57
+ 'monora.yml',
58
+ 'monora.yaml',
59
+ 'monora.json',
60
+ '.monora.yml',
61
+ '.monora.yaml',
62
+ '.monora.json',
63
+ ];
64
+
65
+ return configFiles.some(file =>
66
+ fs.existsSync(path.join(projectRoot, file))
67
+ );
68
+ }
69
+
70
+ async function main() {
71
+ // Skip in CI environments
72
+ if (isCI()) {
73
+ console.log('\nšŸ“¦ Monora SDK installed successfully.');
74
+ console.log(' Run "npx monora init" to configure.\n');
75
+ return;
76
+ }
77
+
78
+ const projectRoot = findProjectRoot();
79
+
80
+ // Check if config already exists
81
+ if (configExists(projectRoot)) {
82
+ console.log('\nāœ… Monora SDK installed. Existing configuration detected.\n');
83
+ return;
84
+ }
85
+
86
+ // Non-interactive terminal - show quick start
87
+ if (!isTTY()) {
88
+ console.log('\nšŸ“¦ Monora SDK installed successfully!');
89
+ console.log('');
90
+ console.log('Quick Start:');
91
+ console.log(' 1. Run: npx monora init');
92
+ console.log(' 2. Or use zero-config:');
93
+ console.log(' import { init } from "monora-ai";');
94
+ console.log(' init();');
95
+ console.log('');
96
+ return;
97
+ }
98
+
99
+ // Interactive terminal - run setup wizard
100
+ console.log('\nšŸš€ Monora SDK installed! Starting setup wizard...\n');
101
+
102
+ try {
103
+ // Run the wizard with --yes for smart defaults
104
+ // Users can run `npx monora init` later for full interactive mode
105
+ const cliPath = path.join(__dirname, '..', 'dist', 'cli.js');
106
+
107
+ if (fs.existsSync(cliPath)) {
108
+ // Run the CLI init command
109
+ const child = spawn('node', [cliPath, 'init', '--yes'], {
110
+ cwd: projectRoot,
111
+ stdio: 'inherit',
112
+ shell: process.platform === 'win32',
113
+ });
114
+
115
+ child.on('error', (err) => {
116
+ console.log('\nšŸ“¦ Monora SDK installed.');
117
+ console.log(' Run "npx monora init" to configure manually.\n');
118
+ });
119
+
120
+ child.on('close', (code) => {
121
+ if (code !== 0) {
122
+ console.log('\n Run "npx monora init" to configure manually.\n');
123
+ }
124
+ });
125
+ } else {
126
+ // CLI not built yet (fresh install), show manual instructions
127
+ console.log('šŸ“¦ Monora SDK installed successfully!');
128
+ console.log('');
129
+ console.log('Quick Start:');
130
+ console.log(' Option 1: Run "npx monora init" to configure');
131
+ console.log(' Option 2: Use zero-config in your code:');
132
+ console.log(' import { init } from "monora-ai";');
133
+ console.log(' init();');
134
+ console.log('');
135
+ }
136
+ } catch (err) {
137
+ // Silent fail - don't break npm install
138
+ console.log('\nšŸ“¦ Monora SDK installed.');
139
+ console.log(' Run "npx monora init" to configure.\n');
140
+ }
141
+ }
142
+
143
+ main().catch(() => {
144
+ // Never fail the install
145
+ console.log('\nšŸ“¦ Monora SDK installed.\n');
146
+ });