@unrdf/observability 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,346 @@
1
+ /**
2
+ * @unrdf/observability - Workflow Metrics Module
3
+ *
4
+ * Prometheus metrics collection for distributed workflow execution.
5
+ * Provides real-time metrics for workflow executions, task completions,
6
+ * resource utilization, and custom business metrics.
7
+ *
8
+ * @module @unrdf/observability/metrics
9
+ */
10
+
11
+ import { register, Counter, Histogram, Gauge, Summary } from 'prom-client';
12
+ import { z } from 'zod';
13
+
14
+ /**
15
+ * Workflow execution status enum
16
+ */
17
+ export const WorkflowStatus = {
18
+ PENDING: 'pending',
19
+ RUNNING: 'running',
20
+ COMPLETED: 'completed',
21
+ FAILED: 'failed',
22
+ CANCELLED: 'cancelled',
23
+ };
24
+
25
+ /**
26
+ * Metric configuration schema
27
+ * @type {z.ZodObject}
28
+ */
29
+ const MetricConfigSchema = z.object({
30
+ enableDefaultMetrics: z.boolean().default(true),
31
+ prefix: z.string().default('unrdf_workflow_'),
32
+ labels: z.record(z.string()).optional(),
33
+ collectInterval: z.number().min(1000).default(10000), // 10s default
34
+ });
35
+
36
+ /**
37
+ * WorkflowMetrics - Comprehensive Prometheus metrics for workflows
38
+ *
39
+ * Collects and exposes metrics for:
40
+ * - Workflow executions (total, active, completed, failed)
41
+ * - Task performance (duration, success rate)
42
+ * - Resource utilization (CPU, memory, queue depth)
43
+ * - Custom business metrics
44
+ *
45
+ * @class
46
+ */
47
+ export class WorkflowMetrics {
48
+ /**
49
+ * @param {object} config - Metric configuration
50
+ * @param {boolean} [config.enableDefaultMetrics=true] - Enable Node.js default metrics
51
+ * @param {string} [config.prefix='unrdf_workflow_'] - Metric name prefix
52
+ * @param {Record<string, string>} [config.labels] - Global labels for all metrics
53
+ * @param {number} [config.collectInterval=10000] - Collection interval in ms
54
+ */
55
+ constructor(config = {}) {
56
+ const validated = MetricConfigSchema.parse(config);
57
+ this.config = validated;
58
+ this.registry = register;
59
+
60
+ // Enable default Node.js metrics (heap, CPU, event loop, etc.)
61
+ if (validated.enableDefaultMetrics) {
62
+ register.setDefaultLabels(validated.labels || {});
63
+ }
64
+
65
+ this._initializeMetrics();
66
+ }
67
+
68
+ /**
69
+ * Initialize all Prometheus metrics
70
+ * @private
71
+ */
72
+ _initializeMetrics() {
73
+ const { prefix } = this.config;
74
+
75
+ // Workflow Execution Metrics
76
+ this.workflowExecutionsTotal = new Counter({
77
+ name: `${prefix}executions_total`,
78
+ help: 'Total number of workflow executions',
79
+ labelNames: ['workflow_id', 'status', 'pattern'],
80
+ });
81
+
82
+ this.workflowExecutionDuration = new Histogram({
83
+ name: `${prefix}execution_duration_seconds`,
84
+ help: 'Workflow execution duration in seconds',
85
+ labelNames: ['workflow_id', 'status', 'pattern'],
86
+ buckets: [0.1, 0.5, 1, 2, 5, 10, 30, 60, 120, 300], // 100ms to 5min
87
+ });
88
+
89
+ this.workflowActiveGauge = new Gauge({
90
+ name: `${prefix}active_workflows`,
91
+ help: 'Number of currently active workflows',
92
+ labelNames: ['workflow_id', 'pattern'],
93
+ });
94
+
95
+ // Task Execution Metrics
96
+ this.taskExecutionsTotal = new Counter({
97
+ name: `${prefix}task_executions_total`,
98
+ help: 'Total number of task executions',
99
+ labelNames: ['workflow_id', 'task_id', 'task_type', 'status'],
100
+ });
101
+
102
+ this.taskExecutionDuration = new Histogram({
103
+ name: `${prefix}task_duration_seconds`,
104
+ help: 'Task execution duration in seconds',
105
+ labelNames: ['workflow_id', 'task_id', 'task_type'],
106
+ buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10, 30], // 10ms to 30s
107
+ });
108
+
109
+ this.taskQueueDepth = new Gauge({
110
+ name: `${prefix}task_queue_depth`,
111
+ help: 'Number of tasks waiting in queue',
112
+ labelNames: ['workflow_id', 'queue_name'],
113
+ });
114
+
115
+ // Resource Utilization Metrics
116
+ this.resourceUtilization = new Gauge({
117
+ name: `${prefix}resource_utilization`,
118
+ help: 'Resource utilization percentage (0-100)',
119
+ labelNames: ['resource_type', 'resource_id'],
120
+ });
121
+
122
+ this.resourceAllocations = new Counter({
123
+ name: `${prefix}resource_allocations_total`,
124
+ help: 'Total resource allocation events',
125
+ labelNames: ['resource_type', 'status'],
126
+ });
127
+
128
+ // Event Sourcing Metrics
129
+ this.eventsAppended = new Counter({
130
+ name: `${prefix}events_appended_total`,
131
+ help: 'Total events appended to event store',
132
+ labelNames: ['event_type', 'workflow_id'],
133
+ });
134
+
135
+ this.eventStoreSize = new Gauge({
136
+ name: `${prefix}event_store_size_bytes`,
137
+ help: 'Size of event store in bytes',
138
+ labelNames: ['workflow_id'],
139
+ });
140
+
141
+ // Business Metrics
142
+ this.policyEvaluations = new Counter({
143
+ name: `${prefix}policy_evaluations_total`,
144
+ help: 'Total policy evaluations',
145
+ labelNames: ['policy_name', 'result'],
146
+ });
147
+
148
+ this.cryptoReceiptGenerations = new Counter({
149
+ name: `${prefix}crypto_receipts_total`,
150
+ help: 'Total cryptographic receipts generated',
151
+ labelNames: ['workflow_id', 'algorithm'],
152
+ });
153
+
154
+ // Performance Metrics
155
+ this.latencyPercentiles = new Summary({
156
+ name: `${prefix}latency_percentiles`,
157
+ help: 'Latency percentiles for workflow operations',
158
+ labelNames: ['operation'],
159
+ percentiles: [0.5, 0.9, 0.95, 0.99],
160
+ });
161
+
162
+ // Error Metrics
163
+ this.errors = new Counter({
164
+ name: `${prefix}errors_total`,
165
+ help: 'Total errors encountered',
166
+ labelNames: ['error_type', 'workflow_id', 'severity'],
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Record workflow execution start
172
+ * @param {string} workflowId - Workflow identifier
173
+ * @param {string} pattern - YAWL pattern type
174
+ */
175
+ recordWorkflowStart(workflowId, pattern = 'unknown') {
176
+ this.workflowActiveGauge.inc({ workflow_id: workflowId, pattern });
177
+ this.workflowExecutionsTotal.inc({ workflow_id: workflowId, status: 'started', pattern });
178
+ }
179
+
180
+ /**
181
+ * Record workflow execution completion
182
+ * @param {string} workflowId - Workflow identifier
183
+ * @param {string} status - Completion status
184
+ * @param {number} durationSeconds - Execution duration in seconds
185
+ * @param {string} pattern - YAWL pattern type
186
+ */
187
+ recordWorkflowComplete(workflowId, status, durationSeconds, pattern = 'unknown') {
188
+ this.workflowActiveGauge.dec({ workflow_id: workflowId, pattern });
189
+ this.workflowExecutionsTotal.inc({ workflow_id: workflowId, status, pattern });
190
+ this.workflowExecutionDuration.observe(
191
+ { workflow_id: workflowId, status, pattern },
192
+ durationSeconds
193
+ );
194
+ }
195
+
196
+ /**
197
+ * Record task execution
198
+ * @param {string} workflowId - Workflow identifier
199
+ * @param {string} taskId - Task identifier
200
+ * @param {string} taskType - Task type
201
+ * @param {string} status - Execution status
202
+ * @param {number} durationSeconds - Execution duration in seconds
203
+ */
204
+ recordTaskExecution(workflowId, taskId, taskType, status, durationSeconds) {
205
+ this.taskExecutionsTotal.inc({
206
+ workflow_id: workflowId,
207
+ task_id: taskId,
208
+ task_type: taskType,
209
+ status,
210
+ });
211
+ this.taskExecutionDuration.observe(
212
+ { workflow_id: workflowId, task_id: taskId, task_type: taskType },
213
+ durationSeconds
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Update task queue depth
219
+ * @param {string} workflowId - Workflow identifier
220
+ * @param {string} queueName - Queue name
221
+ * @param {number} depth - Current queue depth
222
+ */
223
+ updateTaskQueueDepth(workflowId, queueName, depth) {
224
+ this.taskQueueDepth.set({ workflow_id: workflowId, queue_name: queueName }, depth);
225
+ }
226
+
227
+ /**
228
+ * Record resource utilization
229
+ * @param {string} resourceType - Type of resource (cpu, memory, disk, etc.)
230
+ * @param {string} resourceId - Resource identifier
231
+ * @param {number} utilizationPercent - Utilization percentage (0-100)
232
+ */
233
+ recordResourceUtilization(resourceType, resourceId, utilizationPercent) {
234
+ this.resourceUtilization.set(
235
+ { resource_type: resourceType, resource_id: resourceId },
236
+ utilizationPercent
237
+ );
238
+ }
239
+
240
+ /**
241
+ * Record resource allocation event
242
+ * @param {string} resourceType - Type of resource
243
+ * @param {string} status - Allocation status (allocated, deallocated, failed)
244
+ */
245
+ recordResourceAllocation(resourceType, status) {
246
+ this.resourceAllocations.inc({ resource_type: resourceType, status });
247
+ }
248
+
249
+ /**
250
+ * Record event appended to event store
251
+ * @param {string} eventType - Type of event
252
+ * @param {string} workflowId - Workflow identifier
253
+ */
254
+ recordEventAppended(eventType, workflowId) {
255
+ this.eventsAppended.inc({ event_type: eventType, workflow_id: workflowId });
256
+ }
257
+
258
+ /**
259
+ * Update event store size
260
+ * @param {string} workflowId - Workflow identifier
261
+ * @param {number} sizeBytes - Size in bytes
262
+ */
263
+ updateEventStoreSize(workflowId, sizeBytes) {
264
+ this.eventStoreSize.set({ workflow_id: workflowId }, sizeBytes);
265
+ }
266
+
267
+ /**
268
+ * Record policy evaluation
269
+ * @param {string} policyName - Policy name
270
+ * @param {string} result - Evaluation result (allow, deny, error)
271
+ */
272
+ recordPolicyEvaluation(policyName, result) {
273
+ this.policyEvaluations.inc({ policy_name: policyName, result });
274
+ }
275
+
276
+ /**
277
+ * Record cryptographic receipt generation
278
+ * @param {string} workflowId - Workflow identifier
279
+ * @param {string} algorithm - Hash algorithm (BLAKE3, SHA256, etc.)
280
+ */
281
+ recordCryptoReceipt(workflowId, algorithm = 'BLAKE3') {
282
+ this.cryptoReceiptGenerations.inc({ workflow_id: workflowId, algorithm });
283
+ }
284
+
285
+ /**
286
+ * Record operation latency for percentile calculation
287
+ * @param {string} operation - Operation name
288
+ * @param {number} latencyMs - Latency in milliseconds
289
+ */
290
+ recordLatency(operation, latencyMs) {
291
+ this.latencyPercentiles.observe({ operation }, latencyMs / 1000);
292
+ }
293
+
294
+ /**
295
+ * Record error
296
+ * @param {string} errorType - Error type
297
+ * @param {string} workflowId - Workflow identifier
298
+ * @param {string} severity - Error severity (low, medium, high, critical)
299
+ */
300
+ recordError(errorType, workflowId, severity = 'medium') {
301
+ this.errors.inc({ error_type: errorType, workflow_id: workflowId, severity });
302
+ }
303
+
304
+ /**
305
+ * Get all metrics in Prometheus format
306
+ * @returns {Promise<string>} Prometheus metrics text format
307
+ */
308
+ async getMetrics() {
309
+ return this.registry.metrics();
310
+ }
311
+
312
+ /**
313
+ * Get metrics as JSON
314
+ * @returns {Promise<object[]>} Metrics in JSON format
315
+ */
316
+ async getMetricsJSON() {
317
+ return this.registry.getMetricsAsJSON();
318
+ }
319
+
320
+ /**
321
+ * Clear all metrics (useful for testing)
322
+ */
323
+ clearMetrics() {
324
+ this.registry.clear();
325
+ this._initializeMetrics();
326
+ }
327
+
328
+ /**
329
+ * Get metric registry
330
+ * @returns {object} Prometheus registry
331
+ */
332
+ getRegistry() {
333
+ return this.registry;
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Create a workflow metrics instance
339
+ * @param {object} config - Metric configuration
340
+ * @returns {WorkflowMetrics} Metrics instance
341
+ */
342
+ export function createWorkflowMetrics(config = {}) {
343
+ return new WorkflowMetrics(config);
344
+ }
345
+
346
+ export default WorkflowMetrics;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Receipt Anchoring - External timestamping and verification
3
+ *
4
+ * Provides anchoring to external systems:
5
+ * - Blockchain (Ethereum, Bitcoin, etc.)
6
+ * - Git repositories (commit SHA)
7
+ * - Timestamp authorities (RFC 3161)
8
+ *
9
+ * @module @unrdf/observability/receipts/anchor
10
+ */
11
+
12
+ import { AnchorSchema } from './receipt-schema.mjs';
13
+
14
+ /**
15
+ * ReceiptAnchorer - Anchor Merkle roots to external systems
16
+ *
17
+ * @example
18
+ * const anchorer = new ReceiptAnchorer();
19
+ * const anchor = await anchorer.anchorToGit(merkleRoot, commitSha, repo);
20
+ */
21
+ export class ReceiptAnchorer {
22
+ /**
23
+ * Anchor Merkle root to Git repository
24
+ *
25
+ * @param {string} merkleRoot - Merkle root hash to anchor
26
+ * @param {string} commitSha - Git commit SHA
27
+ * @param {string} repository - Repository identifier
28
+ * @returns {Promise<Object>} Anchor proof
29
+ */
30
+ async anchorToGit(merkleRoot, commitSha, repository) {
31
+ const anchor = {
32
+ merkleRoot,
33
+ anchorType: 'git',
34
+ anchorData: {
35
+ commitSha,
36
+ repository,
37
+ },
38
+ timestamp: new Date().toISOString(),
39
+ };
40
+
41
+ return AnchorSchema.parse(anchor);
42
+ }
43
+
44
+ /**
45
+ * Anchor Merkle root to blockchain
46
+ *
47
+ * @param {string} merkleRoot - Merkle root hash to anchor
48
+ * @param {string} txHash - Transaction hash
49
+ * @param {number} blockNumber - Block number
50
+ * @param {string} network - Network name (e.g., 'ethereum', 'bitcoin')
51
+ * @returns {Promise<Object>} Anchor proof
52
+ */
53
+ async anchorToBlockchain(merkleRoot, txHash, blockNumber, network) {
54
+ const anchor = {
55
+ merkleRoot,
56
+ anchorType: 'blockchain',
57
+ anchorData: {
58
+ txHash,
59
+ blockNumber,
60
+ network,
61
+ },
62
+ timestamp: new Date().toISOString(),
63
+ };
64
+
65
+ return AnchorSchema.parse(anchor);
66
+ }
67
+
68
+ /**
69
+ * Anchor Merkle root to timestamp authority
70
+ *
71
+ * @param {string} merkleRoot - Merkle root hash to anchor
72
+ * @param {string} timestampToken - RFC 3161 timestamp token
73
+ * @param {string} authority - Timestamp authority identifier
74
+ * @returns {Promise<Object>} Anchor proof
75
+ */
76
+ async anchorToTimestampService(merkleRoot, timestampToken, authority) {
77
+ const anchor = {
78
+ merkleRoot,
79
+ anchorType: 'timestamp-service',
80
+ anchorData: {
81
+ timestampToken,
82
+ authority,
83
+ },
84
+ timestamp: new Date().toISOString(),
85
+ };
86
+
87
+ return AnchorSchema.parse(anchor);
88
+ }
89
+
90
+ /**
91
+ * Verify an anchor proof
92
+ *
93
+ * @param {Object} anchor - Anchor to verify
94
+ * @returns {Promise<Object>} Verification result
95
+ */
96
+ async verifyAnchor(anchor) {
97
+ try {
98
+ AnchorSchema.parse(anchor);
99
+
100
+ // Basic validation - actual verification would query external systems
101
+ const errors = [];
102
+
103
+ if (anchor.anchorType === 'git') {
104
+ if (!anchor.anchorData.commitSha || !anchor.anchorData.repository) {
105
+ errors.push('Git anchor missing commitSha or repository');
106
+ }
107
+ } else if (anchor.anchorType === 'blockchain') {
108
+ if (!anchor.anchorData.txHash || !anchor.anchorData.network) {
109
+ errors.push('Blockchain anchor missing txHash or network');
110
+ }
111
+ } else if (anchor.anchorType === 'timestamp-service') {
112
+ if (!anchor.anchorData.timestampToken || !anchor.anchorData.authority) {
113
+ errors.push('Timestamp anchor missing token or authority');
114
+ }
115
+ }
116
+
117
+ return {
118
+ valid: errors.length === 0,
119
+ errors,
120
+ anchorType: anchor.anchorType,
121
+ timestamp: anchor.timestamp,
122
+ };
123
+ } catch (err) {
124
+ return {
125
+ valid: false,
126
+ errors: ['Anchor validation failed: ' + err.message],
127
+ };
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Export anchor for external verification
133
+ *
134
+ * @param {Object} anchor - Anchor to export
135
+ * @returns {string} Base64-encoded anchor
136
+ */
137
+ exportAnchor(anchor) {
138
+ const json = JSON.stringify(anchor);
139
+ return Buffer.from(json, 'utf8').toString('base64');
140
+ }
141
+
142
+ /**
143
+ * Import anchor from external source
144
+ *
145
+ * @param {string} base64Anchor - Base64-encoded anchor
146
+ * @returns {Object} Anchor object
147
+ */
148
+ importAnchor(base64Anchor) {
149
+ const json = Buffer.from(base64Anchor, 'base64').toString('utf8');
150
+ const anchor = JSON.parse(json);
151
+ return AnchorSchema.parse(anchor);
152
+ }
153
+ }
154
+
155
+ export default ReceiptAnchorer;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Receipts System - Unified API
3
+ *
4
+ * Tamper-evident audit trails with:
5
+ * - Hash-chained receipts
6
+ * - Merkle tree batching
7
+ * - Tamper detection
8
+ * - External anchoring
9
+ *
10
+ * @module @unrdf/observability/receipts
11
+ */
12
+
13
+ import { ReceiptChain as Chain } from './receipt-chain.mjs';
14
+ import { MerkleTree as Tree } from './merkle-tree.mjs';
15
+ import { TamperDetector as Detector } from './tamper-detection.mjs';
16
+ import { ReceiptAnchorer as Anchorer } from './anchor.mjs';
17
+
18
+ import {
19
+ ReceiptSchema,
20
+ MerkleProofSchema,
21
+ AnchorSchema,
22
+ VerificationResultSchema,
23
+ ChainExportSchema,
24
+ } from './receipt-schema.mjs';
25
+
26
+ export const ReceiptChain = Chain;
27
+ export const MerkleTree = Tree;
28
+ export const TamperDetector = Detector;
29
+ export const ReceiptAnchorer = Anchorer;
30
+
31
+ export {
32
+ ReceiptSchema,
33
+ MerkleProofSchema,
34
+ AnchorSchema,
35
+ VerificationResultSchema,
36
+ ChainExportSchema,
37
+ };
38
+
39
+ /**
40
+ * Quick start: Create receipt chain with tamper detection
41
+ *
42
+ * @example
43
+ * import { ReceiptChain, TamperDetector } from '@unrdf/observability/receipts';
44
+ *
45
+ * const chain = new ReceiptChain('audit-1');
46
+ * await chain.append({
47
+ * operation: 'admit',
48
+ * payload: { delta: 'delta_001' },
49
+ * actor: 'system'
50
+ * });
51
+ *
52
+ * const detector = new TamperDetector();
53
+ * const result = await detector.verifyChain(chain.getAllReceipts());
54
+ * console.log(result.valid); // true
55
+ */
56
+
57
+ export default {
58
+ ReceiptChain,
59
+ MerkleTree,
60
+ TamperDetector,
61
+ ReceiptAnchorer,
62
+ };