@unrdf/kgc-probe 26.4.2

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.
@@ -0,0 +1,397 @@
1
+ /**
2
+ * @fileoverview KGC Probe - Custom Error Classes
3
+ *
4
+ * Provides structured error types with:
5
+ * - Error codes for programmatic handling
6
+ * - Context information for debugging
7
+ * - Recovery suggestions for users
8
+ *
9
+ * @module @unrdf/kgc-probe/utils/errors
10
+ */
11
+
12
+ /**
13
+ * Base error for KGC Probe operations
14
+ *
15
+ * @class ProbeError
16
+ * @extends Error
17
+ * @example
18
+ * throw new ProbeError('Operation failed', 'OP_FAILED', { operation: 'scan' });
19
+ */
20
+ export class ProbeError extends Error {
21
+ /**
22
+ * @param {string} message - Error message
23
+ * @param {string} [code='PROBE_ERROR'] - Error code
24
+ * @param {Object} [context] - Additional context
25
+ * @param {string} [recovery] - Recovery suggestion
26
+ */
27
+ constructor(message, code = 'PROBE_ERROR', context = {}, recovery = undefined) {
28
+ super(message);
29
+ this.name = 'ProbeError';
30
+
31
+ /** @type {string} */
32
+ this.code = code;
33
+
34
+ /** @type {Object} */
35
+ this.context = context;
36
+
37
+ /** @type {string | undefined} */
38
+ this.recovery = recovery;
39
+
40
+ /** @type {string} */
41
+ this.timestamp = new Date().toISOString();
42
+
43
+ // Capture stack trace
44
+ if (Error.captureStackTrace) {
45
+ Error.captureStackTrace(this, this.constructor);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Convert to JSON for logging
51
+ * @returns {Object}
52
+ */
53
+ toJSON() {
54
+ return {
55
+ name: this.name,
56
+ code: this.code,
57
+ message: this.message,
58
+ context: this.context,
59
+ recovery: this.recovery,
60
+ timestamp: this.timestamp,
61
+ stack: this.stack
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Create from unknown error
67
+ * @param {unknown} error - Unknown error
68
+ * @param {string} [code] - Override code
69
+ * @returns {ProbeError}
70
+ */
71
+ static from(error, code) {
72
+ if (error instanceof ProbeError) {
73
+ return error;
74
+ }
75
+
76
+ if (error instanceof Error) {
77
+ return new ProbeError(error.message, code || 'UNKNOWN_ERROR', {
78
+ originalName: error.name,
79
+ originalStack: error.stack
80
+ });
81
+ }
82
+
83
+ return new ProbeError(String(error), code || 'UNKNOWN_ERROR');
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Error thrown when a guard blocks an operation
89
+ *
90
+ * @class GuardViolationError
91
+ * @extends ProbeError
92
+ * @example
93
+ * throw new GuardViolationError(
94
+ * 'Access to AWS credentials forbidden',
95
+ * 'G-H1-ENV-TOKEN',
96
+ * { variable: 'AWS_SECRET_KEY', pattern: 'AWS_*' },
97
+ * 'receipt-123'
98
+ * );
99
+ */
100
+ export class GuardViolationError extends ProbeError {
101
+ /**
102
+ * @param {string} message - Error message
103
+ * @param {string} guardId - Guard that blocked the operation
104
+ * @param {Object} [context] - Additional context
105
+ * @param {string} [receiptId] - Denial receipt ID
106
+ */
107
+ constructor(message, guardId, context = {}, receiptId = undefined) {
108
+ super(message, 'GUARD_VIOLATION', {
109
+ ...context,
110
+ guardId,
111
+ receiptId
112
+ }, 'Check guard policy configuration or request access exemption');
113
+
114
+ this.name = 'GuardViolationError';
115
+
116
+ /** @type {string} */
117
+ this.guardId = guardId;
118
+
119
+ /** @type {string | undefined} */
120
+ this.receiptId = receiptId;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Error thrown when input validation fails
126
+ *
127
+ * @class ValidationError
128
+ * @extends ProbeError
129
+ * @example
130
+ * throw new ValidationError('Invalid universe ID', { field: 'universe_id', value: null });
131
+ */
132
+ export class ValidationError extends ProbeError {
133
+ /**
134
+ * @param {string} message - Error message
135
+ * @param {Object} [context] - Validation context
136
+ * @param {string} [field] - Field that failed validation
137
+ */
138
+ constructor(message, context = {}, field = undefined) {
139
+ super(message, 'VALIDATION_ERROR', {
140
+ ...context,
141
+ field
142
+ }, 'Check input format and required fields');
143
+
144
+ this.name = 'ValidationError';
145
+
146
+ /** @type {string | undefined} */
147
+ this.field = field;
148
+
149
+ /** @type {any} */
150
+ this.issues = context.issues || [];
151
+ }
152
+
153
+ /**
154
+ * Create from Zod error
155
+ * @param {import('zod').ZodError} zodError - Zod validation error
156
+ * @returns {ValidationError}
157
+ */
158
+ static fromZod(zodError) {
159
+ const issues = zodError.issues.map(i => ({
160
+ path: i.path.join('.'),
161
+ message: i.message,
162
+ code: i.code
163
+ }));
164
+
165
+ const firstIssue = issues[0];
166
+ return new ValidationError(
167
+ `Validation failed: ${firstIssue?.message || 'Invalid input'}`,
168
+ { issues },
169
+ firstIssue?.path
170
+ );
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Error thrown when shard merge conflicts occur
176
+ *
177
+ * @class MergeConflictError
178
+ * @extends ProbeError
179
+ * @example
180
+ * throw new MergeConflictError('Conflicting claims detected', [
181
+ * { claimId: 'cap-1', agents: ['agent-1', 'agent-2'] }
182
+ * ]);
183
+ */
184
+ export class MergeConflictError extends ProbeError {
185
+ /**
186
+ * @param {string} message - Error message
187
+ * @param {Array<{claimId: string, agents: string[]}>} conflicts - Conflict details
188
+ */
189
+ constructor(message, conflicts = []) {
190
+ super(message, 'MERGE_CONFLICT', {
191
+ conflicts,
192
+ conflictCount: conflicts.length
193
+ }, 'Use --on-conflict=list to review conflicts or --on-conflict=merge to auto-resolve');
194
+
195
+ this.name = 'MergeConflictError';
196
+
197
+ /** @type {Array<{claimId: string, agents: string[]}>} */
198
+ this.conflicts = conflicts;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Error thrown when receipt verification fails
204
+ *
205
+ * @class ReceiptError
206
+ * @extends ProbeError
207
+ * @example
208
+ * throw new ReceiptError('Hash chain verification failed', {
209
+ * expectedHash: '0xabc...',
210
+ * actualHash: '0xdef...'
211
+ * });
212
+ */
213
+ export class ReceiptError extends ProbeError {
214
+ /**
215
+ * @param {string} message - Error message
216
+ * @param {Object} [context] - Receipt context
217
+ * @param {string} [verificationStep] - Which verification step failed
218
+ */
219
+ constructor(message, context = {}, verificationStep = undefined) {
220
+ super(message, 'RECEIPT_ERROR', {
221
+ ...context,
222
+ verificationStep
223
+ }, 'Regenerate receipts or check artifact integrity');
224
+
225
+ this.name = 'ReceiptError';
226
+
227
+ /** @type {string | undefined} */
228
+ this.verificationStep = verificationStep;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Error thrown when artifact is not found
234
+ *
235
+ * @class ArtifactNotFoundError
236
+ * @extends ProbeError
237
+ * @example
238
+ * throw new ArtifactNotFoundError('run-123');
239
+ */
240
+ export class ArtifactNotFoundError extends ProbeError {
241
+ /**
242
+ * @param {string} artifactId - Missing artifact ID
243
+ * @param {string} [path] - Path searched
244
+ */
245
+ constructor(artifactId, path = undefined) {
246
+ super(`Artifact not found: ${artifactId}`, 'ARTIFACT_NOT_FOUND', {
247
+ artifactId,
248
+ path
249
+ }, 'Run a probe scan first or check the artifact path');
250
+
251
+ this.name = 'ArtifactNotFoundError';
252
+
253
+ /** @type {string} */
254
+ this.artifactId = artifactId;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Error thrown when command execution times out
260
+ *
261
+ * @class TimeoutError
262
+ * @extends ProbeError
263
+ * @example
264
+ * throw new TimeoutError('Scan timed out', 30000);
265
+ */
266
+ export class TimeoutError extends ProbeError {
267
+ /**
268
+ * @param {string} message - Error message
269
+ * @param {number} timeoutMs - Timeout in milliseconds
270
+ * @param {string} [operation] - Operation that timed out
271
+ */
272
+ constructor(message, timeoutMs, operation = undefined) {
273
+ super(message, 'TIMEOUT', {
274
+ timeoutMs,
275
+ operation
276
+ }, 'Increase --timeout value or check for performance issues');
277
+
278
+ this.name = 'TimeoutError';
279
+
280
+ /** @type {number} */
281
+ this.timeoutMs = timeoutMs;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Error thrown when agent execution fails
287
+ *
288
+ * @class AgentError
289
+ * @extends ProbeError
290
+ * @example
291
+ * throw new AgentError('Agent failed', 'completeness-agent', new Error('Query failed'));
292
+ */
293
+ export class AgentError extends ProbeError {
294
+ /**
295
+ * @param {string} message - Error message
296
+ * @param {string} agentId - Agent that failed
297
+ * @param {Error} [cause] - Underlying error
298
+ */
299
+ constructor(message, agentId, cause = undefined) {
300
+ super(message, 'AGENT_ERROR', {
301
+ agentId,
302
+ cause: cause?.message
303
+ }, 'Check agent configuration and dependencies');
304
+
305
+ this.name = 'AgentError';
306
+
307
+ /** @type {string} */
308
+ this.agentId = agentId;
309
+
310
+ /** @type {Error | undefined} */
311
+ this.cause = cause;
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Error thrown when storage operation fails
317
+ *
318
+ * @class StorageError
319
+ * @extends ProbeError
320
+ * @example
321
+ * throw new StorageError('Failed to write artifact', 'write', { path: '/tmp/artifact.json' });
322
+ */
323
+ export class StorageError extends ProbeError {
324
+ /**
325
+ * @param {string} message - Error message
326
+ * @param {string} operation - Storage operation (read|write|delete)
327
+ * @param {Object} [context] - Additional context
328
+ */
329
+ constructor(message, operation, context = {}) {
330
+ super(message, 'STORAGE_ERROR', {
331
+ ...context,
332
+ operation
333
+ }, 'Check storage permissions and disk space');
334
+
335
+ this.name = 'StorageError';
336
+
337
+ /** @type {string} */
338
+ this.operation = operation;
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Error thrown when configuration is invalid
344
+ *
345
+ * @class ConfigurationError
346
+ * @extends ProbeError
347
+ * @example
348
+ * throw new ConfigurationError('Invalid config file', { path: '.kgc-probe.json' });
349
+ */
350
+ export class ConfigurationError extends ProbeError {
351
+ /**
352
+ * @param {string} message - Error message
353
+ * @param {Object} [context] - Configuration context
354
+ */
355
+ constructor(message, context = {}) {
356
+ super(message, 'CONFIG_ERROR', context, 'Check configuration file format and required fields');
357
+
358
+ this.name = 'ConfigurationError';
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Error code constants
364
+ * @type {Object<string, string>}
365
+ */
366
+ export const ErrorCodes = {
367
+ PROBE_ERROR: 'PROBE_ERROR',
368
+ GUARD_VIOLATION: 'GUARD_VIOLATION',
369
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
370
+ MERGE_CONFLICT: 'MERGE_CONFLICT',
371
+ RECEIPT_ERROR: 'RECEIPT_ERROR',
372
+ ARTIFACT_NOT_FOUND: 'ARTIFACT_NOT_FOUND',
373
+ TIMEOUT: 'TIMEOUT',
374
+ AGENT_ERROR: 'AGENT_ERROR',
375
+ STORAGE_ERROR: 'STORAGE_ERROR',
376
+ CONFIG_ERROR: 'CONFIG_ERROR',
377
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR'
378
+ };
379
+
380
+ /**
381
+ * Check if error is a ProbeError
382
+ * @param {unknown} error - Error to check
383
+ * @returns {boolean}
384
+ */
385
+ export function isProbeError(error) {
386
+ return error instanceof ProbeError;
387
+ }
388
+
389
+ /**
390
+ * Wrap error in ProbeError if not already
391
+ * @param {unknown} error - Error to wrap
392
+ * @param {string} [code] - Error code
393
+ * @returns {ProbeError}
394
+ */
395
+ export function wrapError(error, code) {
396
+ return ProbeError.from(error, code);
397
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @fileoverview KGC Probe - Utilities Index
3
+ *
4
+ * Re-exports all utility modules for convenience.
5
+ *
6
+ * @module @unrdf/kgc-probe/utils
7
+ */
8
+
9
+ // Logger
10
+ export {
11
+ Logger,
12
+ createLogger,
13
+ defaultLogger,
14
+ LOG_LEVELS
15
+ } from './logger.mjs';
16
+
17
+ // Error classes
18
+ export {
19
+ ProbeError,
20
+ GuardViolationError,
21
+ ValidationError,
22
+ MergeConflictError,
23
+ ReceiptError,
24
+ ArtifactNotFoundError,
25
+ TimeoutError,
26
+ AgentError,
27
+ StorageError,
28
+ ConfigurationError,
29
+ ErrorCodes,
30
+ isProbeError,
31
+ wrapError
32
+ } from './errors.mjs';
@@ -0,0 +1,236 @@
1
+ /**
2
+ * @fileoverview KGC Probe - Structured Logger
3
+ *
4
+ * Simple, structured logging utility for observability.
5
+ * No external dependencies - outputs JSON for log aggregation.
6
+ *
7
+ * @module @unrdf/kgc-probe/utils/logger
8
+ */
9
+
10
+ /**
11
+ * Log levels in order of severity
12
+ * @type {Object<string, number>}
13
+ */
14
+ const LOG_LEVELS = {
15
+ debug: 0,
16
+ info: 1,
17
+ warn: 2,
18
+ error: 3,
19
+ silent: 4
20
+ };
21
+
22
+ /**
23
+ * Logger configuration
24
+ * @typedef {Object} LoggerConfig
25
+ * @property {string} [level='info'] - Minimum log level
26
+ * @property {boolean} [json=true] - Output JSON format
27
+ * @property {string} [prefix=''] - Log prefix
28
+ * @property {boolean} [timestamps=true] - Include timestamps
29
+ * @property {Function} [output] - Custom output function
30
+ */
31
+
32
+ /**
33
+ * Structured log entry
34
+ * @typedef {Object} LogEntry
35
+ * @property {string} level - Log level
36
+ * @property {string} message - Log message
37
+ * @property {string} [timestamp] - ISO timestamp
38
+ * @property {Object} [context] - Additional context
39
+ */
40
+
41
+ /**
42
+ * Logger - Structured logging with JSON output
43
+ *
44
+ * @class Logger
45
+ * @example
46
+ * const logger = createLogger({ prefix: 'kgc-probe' });
47
+ * logger.info('Scan started', { universe: 'my-universe' });
48
+ * logger.error('Scan failed', { error: err.message });
49
+ */
50
+ export class Logger {
51
+ /**
52
+ * Create logger instance
53
+ * @param {LoggerConfig} [config] - Logger configuration
54
+ */
55
+ constructor(config = {}) {
56
+ /** @type {string} */
57
+ this.level = config.level || 'info';
58
+
59
+ /** @type {boolean} */
60
+ this.json = config.json !== false;
61
+
62
+ /** @type {string} */
63
+ this.prefix = config.prefix || '';
64
+
65
+ /** @type {boolean} */
66
+ this.timestamps = config.timestamps !== false;
67
+
68
+ /** @type {Function} */
69
+ this.output = config.output || console.log;
70
+
71
+ /** @type {Function} */
72
+ this.errorOutput = config.errorOutput || console.error;
73
+ }
74
+
75
+ /**
76
+ * Check if level should be logged
77
+ * @private
78
+ * @param {string} level - Level to check
79
+ * @returns {boolean}
80
+ */
81
+ shouldLog(level) {
82
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
83
+ }
84
+
85
+ /**
86
+ * Format log entry
87
+ * @private
88
+ * @param {string} level - Log level
89
+ * @param {string} message - Log message
90
+ * @param {Object} [context] - Additional context
91
+ * @returns {string}
92
+ */
93
+ format(level, message, context) {
94
+ /** @type {LogEntry} */
95
+ const entry = {
96
+ level,
97
+ message: this.prefix ? `[${this.prefix}] ${message}` : message
98
+ };
99
+
100
+ if (this.timestamps) {
101
+ entry.timestamp = new Date().toISOString();
102
+ }
103
+
104
+ if (context && Object.keys(context).length > 0) {
105
+ entry.context = context;
106
+ }
107
+
108
+ if (this.json) {
109
+ return JSON.stringify(entry);
110
+ }
111
+
112
+ // Human-readable format
113
+ const ts = this.timestamps ? `[${entry.timestamp}] ` : '';
114
+ const ctx = context ? ` ${JSON.stringify(context)}` : '';
115
+ return `${ts}${level.toUpperCase()}: ${entry.message}${ctx}`;
116
+ }
117
+
118
+ /**
119
+ * Log debug message
120
+ * @param {string} message - Log message
121
+ * @param {Object} [context] - Additional context
122
+ */
123
+ debug(message, context) {
124
+ if (this.shouldLog('debug')) {
125
+ this.output(this.format('debug', message, context));
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Log info message
131
+ * @param {string} message - Log message
132
+ * @param {Object} [context] - Additional context
133
+ */
134
+ info(message, context) {
135
+ if (this.shouldLog('info')) {
136
+ this.output(this.format('info', message, context));
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Log warning message
142
+ * @param {string} message - Log message
143
+ * @param {Object} [context] - Additional context
144
+ */
145
+ warn(message, context) {
146
+ if (this.shouldLog('warn')) {
147
+ this.errorOutput(this.format('warn', message, context));
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Log error message
153
+ * @param {string} message - Log message
154
+ * @param {Object} [context] - Additional context
155
+ */
156
+ error(message, context) {
157
+ if (this.shouldLog('error')) {
158
+ this.errorOutput(this.format('error', message, context));
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Create child logger with additional prefix
164
+ * @param {string} childPrefix - Child prefix
165
+ * @returns {Logger}
166
+ */
167
+ child(childPrefix) {
168
+ const newPrefix = this.prefix
169
+ ? `${this.prefix}:${childPrefix}`
170
+ : childPrefix;
171
+
172
+ return new Logger({
173
+ level: this.level,
174
+ json: this.json,
175
+ prefix: newPrefix,
176
+ timestamps: this.timestamps,
177
+ output: this.output,
178
+ errorOutput: this.errorOutput
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Set log level
184
+ * @param {string} level - New log level
185
+ */
186
+ setLevel(level) {
187
+ if (LOG_LEVELS[level] === undefined) {
188
+ throw new Error(`Invalid log level: ${level}`);
189
+ }
190
+ this.level = level;
191
+ }
192
+
193
+ /**
194
+ * Log with timing
195
+ * @param {string} message - Log message
196
+ * @param {Function} fn - Function to time
197
+ * @returns {Promise<any>} Function result
198
+ */
199
+ async timed(message, fn) {
200
+ const start = Date.now();
201
+ try {
202
+ const result = await fn();
203
+ const duration = Date.now() - start;
204
+ this.info(message, { duration_ms: duration, status: 'success' });
205
+ return result;
206
+ } catch (err) {
207
+ const duration = Date.now() - start;
208
+ this.error(message, { duration_ms: duration, status: 'error', error: err.message });
209
+ throw err;
210
+ }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Create logger instance
216
+ * @param {LoggerConfig} [config] - Logger configuration
217
+ * @returns {Logger}
218
+ * @example
219
+ * const logger = createLogger({ prefix: 'my-module', level: 'debug' });
220
+ * logger.info('Hello', { key: 'value' });
221
+ */
222
+ export function createLogger(config) {
223
+ return new Logger(config);
224
+ }
225
+
226
+ /**
227
+ * Default logger instance
228
+ * @type {Logger}
229
+ */
230
+ export const defaultLogger = createLogger({ prefix: 'kgc-probe' });
231
+
232
+ /**
233
+ * Log levels constant
234
+ * @type {Object<string, number>}
235
+ */
236
+ export { LOG_LEVELS };