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
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Human oversight tracking for EU AI Act Art.14 and ISO 42001 8.3 compliance.
|
|
4
|
+
*
|
|
5
|
+
* This module provides human review and override tracking for AI decisions,
|
|
6
|
+
* supporting EU AI Act Article 14 human oversight requirements and ISO 42001
|
|
7
|
+
* control 8.3.
|
|
8
|
+
*
|
|
9
|
+
* Cross-SDK Parity:
|
|
10
|
+
* Both Python and Node.js SDKs provide identical human oversight APIs:
|
|
11
|
+
* - recordHumanReview() / record_human_review()
|
|
12
|
+
* - recordHumanOverride() / record_human_override()
|
|
13
|
+
* - getPendingReviews() / get_pending_reviews()
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.SqliteReviewStore = exports.MemoryReviewStore = void 0;
|
|
50
|
+
exports.reviewToEventBody = reviewToEventBody;
|
|
51
|
+
exports.overrideToEventBody = overrideToEventBody;
|
|
52
|
+
exports.setReviewStore = setReviewStore;
|
|
53
|
+
exports.recordHumanReview = recordHumanReview;
|
|
54
|
+
exports.recordHumanOverride = recordHumanOverride;
|
|
55
|
+
exports.requireReview = requireReview;
|
|
56
|
+
exports.getPendingReviews = getPendingReviews;
|
|
57
|
+
exports.getTimedOutReviews = getTimedOutReviews;
|
|
58
|
+
exports.checkReviewRequired = checkReviewRequired;
|
|
59
|
+
exports.getOversightSummary = getOversightSummary;
|
|
60
|
+
exports.clearOversightData = clearOversightData;
|
|
61
|
+
const crypto = __importStar(require("crypto"));
|
|
62
|
+
/**
|
|
63
|
+
* Convert HumanReview to event body dictionary.
|
|
64
|
+
*/
|
|
65
|
+
function reviewToEventBody(review) {
|
|
66
|
+
const body = {
|
|
67
|
+
reviewed_event_id: review.reviewedEventId,
|
|
68
|
+
reviewer_id: review.reviewerId,
|
|
69
|
+
review_type: review.reviewType,
|
|
70
|
+
decision: review.decision,
|
|
71
|
+
timestamp: review.timestamp,
|
|
72
|
+
};
|
|
73
|
+
if (review.modifications) {
|
|
74
|
+
body.modifications = review.modifications;
|
|
75
|
+
}
|
|
76
|
+
if (review.reviewDurationMs !== undefined) {
|
|
77
|
+
body.review_duration_ms = review.reviewDurationMs;
|
|
78
|
+
}
|
|
79
|
+
if (review.reviewNotes) {
|
|
80
|
+
body.review_notes = review.reviewNotes;
|
|
81
|
+
}
|
|
82
|
+
if (review.complianceFlags.length > 0) {
|
|
83
|
+
body.compliance_flags = review.complianceFlags;
|
|
84
|
+
}
|
|
85
|
+
return body;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Convert HumanOverride to event body dictionary.
|
|
89
|
+
*/
|
|
90
|
+
function overrideToEventBody(override) {
|
|
91
|
+
const body = {
|
|
92
|
+
overridden_event_id: override.overriddenEventId,
|
|
93
|
+
override_reason: override.overrideReason,
|
|
94
|
+
timestamp: override.timestamp,
|
|
95
|
+
};
|
|
96
|
+
if (override.originalOutputHash) {
|
|
97
|
+
body.original_output_hash = override.originalOutputHash;
|
|
98
|
+
}
|
|
99
|
+
if (override.correctedOutputHash) {
|
|
100
|
+
body.corrected_output_hash = override.correctedOutputHash;
|
|
101
|
+
}
|
|
102
|
+
if (override.overrideAuthority) {
|
|
103
|
+
body.override_authority = override.overrideAuthority;
|
|
104
|
+
}
|
|
105
|
+
return body;
|
|
106
|
+
}
|
|
107
|
+
const DEFAULT_COMPLETED_REVIEW_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
108
|
+
/**
|
|
109
|
+
* In-memory store for oversight reviews.
|
|
110
|
+
*
|
|
111
|
+
* Note: This is single-instance only. For production/distributed use, supply
|
|
112
|
+
* a persistent store via setReviewStore (e.g., SqliteReviewStore or Redis).
|
|
113
|
+
*/
|
|
114
|
+
class MemoryReviewStore {
|
|
115
|
+
constructor(options) {
|
|
116
|
+
this.pendingReviews = new Map();
|
|
117
|
+
this.completedReviews = [];
|
|
118
|
+
this.overrides = [];
|
|
119
|
+
this.completedTtlMs = options?.completedTtlMs ?? DEFAULT_COMPLETED_REVIEW_TTL_MS;
|
|
120
|
+
}
|
|
121
|
+
addPendingReview(review) {
|
|
122
|
+
this.pendingReviews.set(review.eventId, review);
|
|
123
|
+
}
|
|
124
|
+
removePendingReview(eventId) {
|
|
125
|
+
this.pendingReviews.delete(eventId);
|
|
126
|
+
}
|
|
127
|
+
listPendingReviews() {
|
|
128
|
+
return Array.from(this.pendingReviews.values());
|
|
129
|
+
}
|
|
130
|
+
listTimedOutReviews(now) {
|
|
131
|
+
return this.listPendingReviews().filter((review) => review.timeoutAt < now);
|
|
132
|
+
}
|
|
133
|
+
addCompletedReview(review) {
|
|
134
|
+
this.pruneCompletedReviews(Date.now());
|
|
135
|
+
this.completedReviews.push(review);
|
|
136
|
+
}
|
|
137
|
+
listCompletedReviews() {
|
|
138
|
+
this.pruneCompletedReviews(Date.now());
|
|
139
|
+
return [...this.completedReviews];
|
|
140
|
+
}
|
|
141
|
+
addOverride(override) {
|
|
142
|
+
this.overrides.push(override);
|
|
143
|
+
}
|
|
144
|
+
listOverrides() {
|
|
145
|
+
return [...this.overrides];
|
|
146
|
+
}
|
|
147
|
+
clear() {
|
|
148
|
+
this.pendingReviews.clear();
|
|
149
|
+
this.completedReviews = [];
|
|
150
|
+
this.overrides = [];
|
|
151
|
+
}
|
|
152
|
+
pruneCompletedReviews(now) {
|
|
153
|
+
if (this.completedTtlMs <= 0) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const cutoff = now - this.completedTtlMs;
|
|
157
|
+
this.completedReviews = this.completedReviews.filter((review) => {
|
|
158
|
+
const timestampMs = Date.parse(review.timestamp);
|
|
159
|
+
if (Number.isNaN(timestampMs)) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return timestampMs >= cutoff;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.MemoryReviewStore = MemoryReviewStore;
|
|
167
|
+
/**
|
|
168
|
+
* SQLite-backed persistent review store (requires better-sqlite3).
|
|
169
|
+
*/
|
|
170
|
+
class SqliteReviewStore {
|
|
171
|
+
constructor(dbPath, options) {
|
|
172
|
+
let Database;
|
|
173
|
+
try {
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
175
|
+
Database = require('better-sqlite3');
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
179
|
+
throw new Error(`SqliteReviewStore requires better-sqlite3. ${message}`);
|
|
180
|
+
}
|
|
181
|
+
this.db = new Database(dbPath);
|
|
182
|
+
this.completedTtlMs = options?.completedTtlMs ?? DEFAULT_COMPLETED_REVIEW_TTL_MS;
|
|
183
|
+
this.db.exec(`
|
|
184
|
+
CREATE TABLE IF NOT EXISTS pending_reviews (
|
|
185
|
+
event_id TEXT PRIMARY KEY,
|
|
186
|
+
trace_id TEXT,
|
|
187
|
+
event_type TEXT,
|
|
188
|
+
model TEXT,
|
|
189
|
+
risk_level TEXT,
|
|
190
|
+
created_at INTEGER,
|
|
191
|
+
timeout_at INTEGER
|
|
192
|
+
);
|
|
193
|
+
CREATE TABLE IF NOT EXISTS completed_reviews (
|
|
194
|
+
reviewed_event_id TEXT,
|
|
195
|
+
reviewer_id TEXT,
|
|
196
|
+
review_type TEXT,
|
|
197
|
+
decision TEXT,
|
|
198
|
+
modifications TEXT,
|
|
199
|
+
review_duration_ms INTEGER,
|
|
200
|
+
review_notes TEXT,
|
|
201
|
+
compliance_flags TEXT,
|
|
202
|
+
timestamp TEXT,
|
|
203
|
+
timestamp_ms INTEGER
|
|
204
|
+
);
|
|
205
|
+
CREATE TABLE IF NOT EXISTS overrides (
|
|
206
|
+
overridden_event_id TEXT,
|
|
207
|
+
override_reason TEXT,
|
|
208
|
+
original_output_hash TEXT,
|
|
209
|
+
corrected_output_hash TEXT,
|
|
210
|
+
override_authority TEXT,
|
|
211
|
+
timestamp TEXT
|
|
212
|
+
);
|
|
213
|
+
`);
|
|
214
|
+
}
|
|
215
|
+
addPendingReview(review) {
|
|
216
|
+
this.db.prepare(`
|
|
217
|
+
INSERT OR REPLACE INTO pending_reviews
|
|
218
|
+
(event_id, trace_id, event_type, model, risk_level, created_at, timeout_at)
|
|
219
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
220
|
+
`).run(review.eventId, review.traceId ?? null, review.eventType, review.model ?? null, review.riskLevel ?? null, review.createdAt, review.timeoutAt);
|
|
221
|
+
}
|
|
222
|
+
removePendingReview(eventId) {
|
|
223
|
+
this.db.prepare('DELETE FROM pending_reviews WHERE event_id = ?').run(eventId);
|
|
224
|
+
}
|
|
225
|
+
listPendingReviews() {
|
|
226
|
+
const rows = this.db.prepare('SELECT * FROM pending_reviews').all();
|
|
227
|
+
return rows.map((row) => ({
|
|
228
|
+
eventId: row.event_id,
|
|
229
|
+
traceId: row.trace_id ?? undefined,
|
|
230
|
+
eventType: row.event_type,
|
|
231
|
+
model: row.model ?? undefined,
|
|
232
|
+
riskLevel: row.risk_level ?? undefined,
|
|
233
|
+
createdAt: row.created_at,
|
|
234
|
+
timeoutAt: row.timeout_at,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
listTimedOutReviews(now) {
|
|
238
|
+
const rows = this.db
|
|
239
|
+
.prepare('SELECT * FROM pending_reviews WHERE timeout_at < ?')
|
|
240
|
+
.all(now);
|
|
241
|
+
return rows.map((row) => ({
|
|
242
|
+
eventId: row.event_id,
|
|
243
|
+
traceId: row.trace_id ?? undefined,
|
|
244
|
+
eventType: row.event_type,
|
|
245
|
+
model: row.model ?? undefined,
|
|
246
|
+
riskLevel: row.risk_level ?? undefined,
|
|
247
|
+
createdAt: row.created_at,
|
|
248
|
+
timeoutAt: row.timeout_at,
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
addCompletedReview(review) {
|
|
252
|
+
const timestampMs = Date.parse(review.timestamp);
|
|
253
|
+
this.db.prepare(`
|
|
254
|
+
INSERT INTO completed_reviews
|
|
255
|
+
(reviewed_event_id, reviewer_id, review_type, decision, modifications, review_duration_ms,
|
|
256
|
+
review_notes, compliance_flags, timestamp, timestamp_ms)
|
|
257
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
258
|
+
`).run(review.reviewedEventId, review.reviewerId, review.reviewType, review.decision, review.modifications ?? null, review.reviewDurationMs ?? null, review.reviewNotes ?? null, JSON.stringify(review.complianceFlags || []), review.timestamp, Number.isNaN(timestampMs) ? null : timestampMs);
|
|
259
|
+
this.pruneCompletedReviews(Date.now());
|
|
260
|
+
}
|
|
261
|
+
listCompletedReviews() {
|
|
262
|
+
this.pruneCompletedReviews(Date.now());
|
|
263
|
+
const rows = this.db.prepare('SELECT * FROM completed_reviews').all();
|
|
264
|
+
return rows.map((row) => ({
|
|
265
|
+
reviewedEventId: row.reviewed_event_id,
|
|
266
|
+
reviewerId: row.reviewer_id,
|
|
267
|
+
reviewType: row.review_type,
|
|
268
|
+
decision: row.decision,
|
|
269
|
+
modifications: row.modifications ?? undefined,
|
|
270
|
+
reviewDurationMs: row.review_duration_ms ?? undefined,
|
|
271
|
+
reviewNotes: row.review_notes ?? undefined,
|
|
272
|
+
complianceFlags: this.parseComplianceFlags(row.compliance_flags),
|
|
273
|
+
timestamp: row.timestamp,
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
addOverride(override) {
|
|
277
|
+
this.db.prepare(`
|
|
278
|
+
INSERT INTO overrides
|
|
279
|
+
(overridden_event_id, override_reason, original_output_hash, corrected_output_hash,
|
|
280
|
+
override_authority, timestamp)
|
|
281
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
282
|
+
`).run(override.overriddenEventId, override.overrideReason, override.originalOutputHash ?? null, override.correctedOutputHash ?? null, override.overrideAuthority ?? null, override.timestamp);
|
|
283
|
+
}
|
|
284
|
+
listOverrides() {
|
|
285
|
+
const rows = this.db.prepare('SELECT * FROM overrides').all();
|
|
286
|
+
return rows.map((row) => ({
|
|
287
|
+
overriddenEventId: row.overridden_event_id,
|
|
288
|
+
overrideReason: row.override_reason,
|
|
289
|
+
originalOutputHash: row.original_output_hash ?? undefined,
|
|
290
|
+
correctedOutputHash: row.corrected_output_hash ?? undefined,
|
|
291
|
+
overrideAuthority: row.override_authority ?? undefined,
|
|
292
|
+
timestamp: row.timestamp,
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
clear() {
|
|
296
|
+
this.db.exec('DELETE FROM pending_reviews; DELETE FROM completed_reviews; DELETE FROM overrides;');
|
|
297
|
+
}
|
|
298
|
+
pruneCompletedReviews(now) {
|
|
299
|
+
if (this.completedTtlMs <= 0) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const cutoff = now - this.completedTtlMs;
|
|
303
|
+
this.db.prepare('DELETE FROM completed_reviews WHERE timestamp_ms IS NOT NULL AND timestamp_ms < ?')
|
|
304
|
+
.run(cutoff);
|
|
305
|
+
}
|
|
306
|
+
parseComplianceFlags(value) {
|
|
307
|
+
if (!value) {
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const parsed = JSON.parse(value);
|
|
312
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
exports.SqliteReviewStore = SqliteReviewStore;
|
|
320
|
+
let reviewStore = new MemoryReviewStore();
|
|
321
|
+
function setReviewStore(store) {
|
|
322
|
+
reviewStore = store;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Record a human review of an AI decision.
|
|
326
|
+
*
|
|
327
|
+
* @param reviewedEventId - ID of the event being reviewed.
|
|
328
|
+
* @param reviewerId - ID of the human reviewer.
|
|
329
|
+
* @param reviewType - Type of review (approval, rejection, modification, escalation).
|
|
330
|
+
* @param decision - Review decision (approved, rejected, modified, escalated).
|
|
331
|
+
* @param options - Additional options.
|
|
332
|
+
* @returns The created HumanReview.
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```typescript
|
|
336
|
+
* recordHumanReview(
|
|
337
|
+
* 'evt_123',
|
|
338
|
+
* 'usr_456',
|
|
339
|
+
* 'approval',
|
|
340
|
+
* 'approved',
|
|
341
|
+
* { notes: 'Verified output accuracy' }
|
|
342
|
+
* );
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
function recordHumanReview(reviewedEventId, reviewerId, reviewType, decision, options = {}) {
|
|
346
|
+
const review = {
|
|
347
|
+
reviewedEventId,
|
|
348
|
+
reviewerId,
|
|
349
|
+
reviewType,
|
|
350
|
+
decision,
|
|
351
|
+
modifications: options.modifications,
|
|
352
|
+
reviewDurationMs: options.reviewDurationMs,
|
|
353
|
+
reviewNotes: options.notes,
|
|
354
|
+
complianceFlags: options.complianceFlags || [],
|
|
355
|
+
timestamp: new Date().toISOString(),
|
|
356
|
+
};
|
|
357
|
+
reviewStore.addCompletedReview(review);
|
|
358
|
+
reviewStore.removePendingReview(reviewedEventId);
|
|
359
|
+
return review;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Record a human override of an AI output.
|
|
363
|
+
*
|
|
364
|
+
* @param overriddenEventId - ID of the event being overridden.
|
|
365
|
+
* @param overrideReason - Reason for the override.
|
|
366
|
+
* @param options - Additional options.
|
|
367
|
+
* @returns The created HumanOverride.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```typescript
|
|
371
|
+
* recordHumanOverride(
|
|
372
|
+
* 'evt_123',
|
|
373
|
+
* 'Incorrect recommendation',
|
|
374
|
+
* { overrideAuthority: 'supervisor' }
|
|
375
|
+
* );
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
function recordHumanOverride(overriddenEventId, overrideReason, options = {}) {
|
|
379
|
+
let originalHash;
|
|
380
|
+
let correctedHash;
|
|
381
|
+
if (options.originalOutput) {
|
|
382
|
+
const hash = crypto.createHash('sha256').update(options.originalOutput).digest('hex');
|
|
383
|
+
originalHash = `sha256:${hash}`;
|
|
384
|
+
}
|
|
385
|
+
if (options.correctedOutput) {
|
|
386
|
+
const hash = crypto.createHash('sha256').update(options.correctedOutput).digest('hex');
|
|
387
|
+
correctedHash = `sha256:${hash}`;
|
|
388
|
+
}
|
|
389
|
+
const override = {
|
|
390
|
+
overriddenEventId,
|
|
391
|
+
overrideReason,
|
|
392
|
+
originalOutputHash: originalHash,
|
|
393
|
+
correctedOutputHash: correctedHash,
|
|
394
|
+
overrideAuthority: options.overrideAuthority,
|
|
395
|
+
timestamp: new Date().toISOString(),
|
|
396
|
+
};
|
|
397
|
+
reviewStore.addOverride(override);
|
|
398
|
+
return override;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Mark an event as requiring human review.
|
|
402
|
+
*
|
|
403
|
+
* @param eventId - ID of the event requiring review.
|
|
404
|
+
* @param options - Additional options.
|
|
405
|
+
* @returns The created PendingReview.
|
|
406
|
+
*/
|
|
407
|
+
function requireReview(eventId, options = {}) {
|
|
408
|
+
const config = options.config || {};
|
|
409
|
+
const oversightConfig = config.human_oversight || {};
|
|
410
|
+
const timeoutHours = oversightConfig.review_timeout_hours ?? 24;
|
|
411
|
+
const now = Date.now();
|
|
412
|
+
const pending = {
|
|
413
|
+
eventId,
|
|
414
|
+
traceId: options.traceId,
|
|
415
|
+
eventType: options.eventType || 'llm_call',
|
|
416
|
+
model: options.model,
|
|
417
|
+
riskLevel: options.riskLevel,
|
|
418
|
+
createdAt: now,
|
|
419
|
+
timeoutAt: now + timeoutHours * 3600 * 1000,
|
|
420
|
+
};
|
|
421
|
+
reviewStore.addPendingReview(pending);
|
|
422
|
+
return pending;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get all events pending human review.
|
|
426
|
+
*/
|
|
427
|
+
function getPendingReviews() {
|
|
428
|
+
return reviewStore.listPendingReviews();
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Get reviews that have timed out.
|
|
432
|
+
*/
|
|
433
|
+
function getTimedOutReviews() {
|
|
434
|
+
return reviewStore.listTimedOutReviews(Date.now());
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Check if an event requires human review based on config.
|
|
438
|
+
*
|
|
439
|
+
* @param eventType - Type of event.
|
|
440
|
+
* @param model - Model used.
|
|
441
|
+
* @param riskLevel - Risk level of the event.
|
|
442
|
+
* @param config - Optional config.
|
|
443
|
+
* @returns True if review is required.
|
|
444
|
+
*/
|
|
445
|
+
function checkReviewRequired(eventType, model, riskLevel, config) {
|
|
446
|
+
const oversightConfig = config?.human_oversight;
|
|
447
|
+
if (!oversightConfig?.enabled) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
const requireFor = oversightConfig.require_review_for || [];
|
|
451
|
+
if (requireFor.length === 0) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
for (const condition of requireFor) {
|
|
455
|
+
if (condition.risk_level && riskLevel !== condition.risk_level) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (condition.event_type && eventType !== condition.event_type) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (condition.tool_name && model !== condition.tool_name) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Get summary of human oversight for reporting.
|
|
470
|
+
*
|
|
471
|
+
* @returns Summary dict for compliance reports.
|
|
472
|
+
*/
|
|
473
|
+
function getOversightSummary() {
|
|
474
|
+
const completed = reviewStore.listCompletedReviews();
|
|
475
|
+
const pending = reviewStore.listPendingReviews();
|
|
476
|
+
const overrides = reviewStore.listOverrides();
|
|
477
|
+
const timedOut = reviewStore.listTimedOutReviews(Date.now());
|
|
478
|
+
const reviewTimes = completed
|
|
479
|
+
.filter((r) => r.reviewDurationMs !== undefined)
|
|
480
|
+
.map((r) => r.reviewDurationMs);
|
|
481
|
+
const avgReviewTime = reviewTimes.length > 0
|
|
482
|
+
? reviewTimes.reduce((a, b) => a + b, 0) / reviewTimes.length
|
|
483
|
+
: null;
|
|
484
|
+
return {
|
|
485
|
+
reviews_completed: completed.length,
|
|
486
|
+
reviews_pending: pending.length,
|
|
487
|
+
overrides: overrides.length,
|
|
488
|
+
average_review_time_ms: avgReviewTime,
|
|
489
|
+
timed_out_reviews: timedOut.length,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Clear all oversight tracking data.
|
|
494
|
+
*/
|
|
495
|
+
function clearOversightData() {
|
|
496
|
+
reviewStore.clear();
|
|
497
|
+
}
|
package/dist/pdf_report.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pdf_report.d.ts","sourceRoot":"","sources":["../src/pdf_report.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,QAAA,IAAI,mBAAmB,SAAQ,CAAC;AAWhC,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAE/B;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAkMD,UAAU,gBAAgB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,uBAAuB,CAAC,EAAE,MAAM,CAAC;KAClC,CAAC;IACF,gBAAgB,CAAC,EAAE;QACjB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC/B,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;QACpC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;KAChC,CAAC;CACH;AAED,UAAU,WAAW;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC,CAAC;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClD;AAED,UAAU,SAAS;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;
|
|
1
|
+
{"version":3,"file":"pdf_report.d.ts","sourceRoot":"","sources":["../src/pdf_report.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,QAAA,IAAI,mBAAmB,SAAQ,CAAC;AAWhC,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAE/B;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAkMD,UAAU,gBAAgB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,uBAAuB,CAAC,EAAE,MAAM,CAAC;KAClC,CAAC;IACF,gBAAgB,CAAC,EAAE;QACjB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC/B,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;QACpC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;KAChC,CAAC;CACH;AAED,UAAU,WAAW;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC,CAAC;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClD;AAED,UAAU,SAAS;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAoZD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,SAAS,GACjB,OAAO,CAAC,MAAM,CAAC,CAiDjB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAiDjB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD"}
|
package/dist/pdf_report.js
CHANGED
|
@@ -275,11 +275,19 @@ function formatDate(isoDate) {
|
|
|
275
275
|
return isoDate;
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
|
+
function htmlEscape(value) {
|
|
279
|
+
return value
|
|
280
|
+
.replace(/&/g, '&')
|
|
281
|
+
.replace(/</g, '<')
|
|
282
|
+
.replace(/>/g, '>')
|
|
283
|
+
.replace(/"/g, '"')
|
|
284
|
+
.replace(/'/g, ''');
|
|
285
|
+
}
|
|
278
286
|
/**
|
|
279
287
|
* Generate HTML for compliance report.
|
|
280
288
|
*/
|
|
281
289
|
function generateComplianceHtml(report, config) {
|
|
282
|
-
const orgName = config?.organization_name || 'Organization';
|
|
290
|
+
const orgName = htmlEscape(String(config?.organization_name || 'Organization'));
|
|
283
291
|
const totalEvents = report.total_events || 0;
|
|
284
292
|
const traces = report.traces || 0;
|
|
285
293
|
const dateRange = report.date_range || {};
|
|
@@ -288,8 +296,8 @@ function generateComplianceHtml(report, config) {
|
|
|
288
296
|
const violations = report.violations || [];
|
|
289
297
|
const tokenUsage = report.token_usage || {};
|
|
290
298
|
const modelCompliance = report.model_compliance || {};
|
|
291
|
-
const startDate = formatDate(dateRange.start);
|
|
292
|
-
const endDate = formatDate(dateRange.end);
|
|
299
|
+
const startDate = htmlEscape(formatDate(dateRange.start));
|
|
300
|
+
const endDate = htmlEscape(formatDate(dateRange.end));
|
|
293
301
|
// Build event type rows
|
|
294
302
|
let eventTypeRows = '';
|
|
295
303
|
const sortedEventTypes = Object.entries(byEventType).sort((a, b) => b[1] - a[1]);
|
|
@@ -297,7 +305,7 @@ function generateComplianceHtml(report, config) {
|
|
|
297
305
|
const pct = totalEvents > 0 ? (count / totalEvents) * 100 : 0;
|
|
298
306
|
eventTypeRows += `
|
|
299
307
|
<tr>
|
|
300
|
-
<td>${eventType}</td>
|
|
308
|
+
<td>${htmlEscape(String(eventType))}</td>
|
|
301
309
|
<td>${formatNumber(count)}</td>
|
|
302
310
|
<td>
|
|
303
311
|
<div class="metric-bar">
|
|
@@ -314,7 +322,7 @@ function generateComplianceHtml(report, config) {
|
|
|
314
322
|
for (const [model, count] of sortedModels) {
|
|
315
323
|
modelRows += `
|
|
316
324
|
<tr>
|
|
317
|
-
<td>${model}</td>
|
|
325
|
+
<td>${htmlEscape(String(model))}</td>
|
|
318
326
|
<td>${formatNumber(count)}</td>
|
|
319
327
|
</tr>
|
|
320
328
|
`;
|
|
@@ -324,12 +332,16 @@ function generateComplianceHtml(report, config) {
|
|
|
324
332
|
if (violations.length > 0) {
|
|
325
333
|
violationsHtml = '<div class="violation-list"><h4>Policy Violations</h4>';
|
|
326
334
|
for (const v of violations.slice(0, 20)) {
|
|
335
|
+
const safeTimestamp = htmlEscape(String(v.timestamp || 'N/A'));
|
|
336
|
+
const safeModel = htmlEscape(String(v.model || 'N/A'));
|
|
337
|
+
const safePolicy = htmlEscape(String(v.policy || 'N/A'));
|
|
338
|
+
const safeMessage = htmlEscape(String(v.message || ''));
|
|
327
339
|
violationsHtml += `
|
|
328
340
|
<div class="violation-item">
|
|
329
|
-
<strong>${
|
|
330
|
-
Model: <code>${
|
|
331
|
-
Policy: ${
|
|
332
|
-
${
|
|
341
|
+
<strong>${safeTimestamp}</strong> -
|
|
342
|
+
Model: <code>${safeModel}</code> |
|
|
343
|
+
Policy: ${safePolicy} |
|
|
344
|
+
${safeMessage}
|
|
333
345
|
</div>
|
|
334
346
|
`;
|
|
335
347
|
}
|
|
@@ -344,6 +356,12 @@ function generateComplianceHtml(report, config) {
|
|
|
344
356
|
const allowedModels = modelCompliance.allowed_models_used || [];
|
|
345
357
|
const forbiddenBlocked = modelCompliance.forbidden_models_blocked || [];
|
|
346
358
|
const unknownModels = modelCompliance.unknown_models_used || [];
|
|
359
|
+
const allowedModelsLabel = allowedModels.length
|
|
360
|
+
? allowedModels.map((model) => htmlEscape(String(model))).join(', ')
|
|
361
|
+
: 'None';
|
|
362
|
+
const unknownModelsLabel = unknownModels.length
|
|
363
|
+
? unknownModels.map((model) => htmlEscape(String(model))).join(', ')
|
|
364
|
+
: 'None';
|
|
347
365
|
const complianceStatus = violations.length === 0 && forbiddenBlocked.length === 0
|
|
348
366
|
? 'success'
|
|
349
367
|
: violations.length < 5
|
|
@@ -447,11 +465,11 @@ function generateComplianceHtml(report, config) {
|
|
|
447
465
|
<div class="two-column">
|
|
448
466
|
<div class="card">
|
|
449
467
|
<h4>Allowed Models Used</h4>
|
|
450
|
-
<p>${
|
|
468
|
+
<p>${allowedModelsLabel}</p>
|
|
451
469
|
</div>
|
|
452
470
|
<div class="card">
|
|
453
471
|
<h4>Unknown Models</h4>
|
|
454
|
-
<p>${
|
|
472
|
+
<p>${unknownModelsLabel}</p>
|
|
455
473
|
</div>
|
|
456
474
|
</div>
|
|
457
475
|
|
|
@@ -467,9 +485,9 @@ function generateComplianceHtml(report, config) {
|
|
|
467
485
|
* Generate HTML for AI Act transparency report.
|
|
468
486
|
*/
|
|
469
487
|
function generateAIActHtml(report) {
|
|
470
|
-
const orgName = report.organizationName || 'Organization';
|
|
471
|
-
const periodStart = report.reportingPeriodStart || 'N/A';
|
|
472
|
-
const periodEnd = report.reportingPeriodEnd || 'N/A';
|
|
488
|
+
const orgName = htmlEscape(String(report.organizationName || 'Organization'));
|
|
489
|
+
const periodStart = htmlEscape(String(report.reportingPeriodStart || 'N/A'));
|
|
490
|
+
const periodEnd = htmlEscape(String(report.reportingPeriodEnd || 'N/A'));
|
|
473
491
|
const totalInteractions = report.totalAiInteractions || 0;
|
|
474
492
|
const uniqueModels = report.uniqueModelsUsed || 0;
|
|
475
493
|
const uniqueTraces = report.uniqueTraces || 0;
|
|
@@ -494,12 +512,15 @@ function generateAIActHtml(report) {
|
|
|
494
512
|
let modelRows = '';
|
|
495
513
|
for (const model of modelsUsed) {
|
|
496
514
|
const riskCat = model.riskCategory || 'limited';
|
|
497
|
-
const
|
|
515
|
+
const riskLabel = htmlEscape(String(riskCat).toUpperCase());
|
|
516
|
+
const safeModel = htmlEscape(String(model.model || 'N/A'));
|
|
517
|
+
const safeProvider = htmlEscape(String(model.provider || 'N/A'));
|
|
518
|
+
const caps = htmlEscape(String((model.capabilities || []).slice(0, 3).join(', ') || 'N/A'));
|
|
498
519
|
modelRows += `
|
|
499
520
|
<tr>
|
|
500
|
-
<td>${
|
|
501
|
-
<td>${
|
|
502
|
-
<td><span class="status-badge risk-${riskCat}">${
|
|
521
|
+
<td>${safeModel}</td>
|
|
522
|
+
<td>${safeProvider}</td>
|
|
523
|
+
<td><span class="status-badge risk-${riskCat}">${riskLabel}</span></td>
|
|
503
524
|
<td>${formatNumber(model.callCount || 0)}</td>
|
|
504
525
|
<td>${caps}</td>
|
|
505
526
|
</tr>
|
|
@@ -511,7 +532,7 @@ function generateAIActHtml(report) {
|
|
|
511
532
|
for (const [classification, count] of sortedClassifications) {
|
|
512
533
|
classificationRows += `
|
|
513
534
|
<tr>
|
|
514
|
-
<td>${classification}</td>
|
|
535
|
+
<td>${htmlEscape(String(classification))}</td>
|
|
515
536
|
<td>${formatNumber(count)}</td>
|
|
516
537
|
</tr>
|
|
517
538
|
`;
|
|
@@ -521,10 +542,10 @@ function generateAIActHtml(report) {
|
|
|
521
542
|
if (highRisk.length > 0 || unacceptableRisk.length > 0) {
|
|
522
543
|
warningsHtml = '<div class="violation-list"><h4>⚠️ Risk Warnings</h4>';
|
|
523
544
|
if (unacceptableRisk.length > 0) {
|
|
524
|
-
warningsHtml += `<p class="status-badge status-error">UNACCEPTABLE RISK: ${unacceptableRisk.join(', ')}</p>`;
|
|
545
|
+
warningsHtml += `<p class="status-badge status-error">UNACCEPTABLE RISK: ${unacceptableRisk.map((model) => htmlEscape(String(model))).join(', ')}</p>`;
|
|
525
546
|
}
|
|
526
547
|
if (highRisk.length > 0) {
|
|
527
|
-
warningsHtml += `<p class="status-badge status-warning">HIGH RISK: ${highRisk.join(', ')}</p>`;
|
|
548
|
+
warningsHtml += `<p class="status-badge status-warning">HIGH RISK: ${highRisk.map((model) => htmlEscape(String(model))).join(', ')}</p>`;
|
|
528
549
|
}
|
|
529
550
|
warningsHtml += '</div>';
|
|
530
551
|
}
|