@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.
- package/.eslintrc.cjs +10 -0
- package/IMPLEMENTATION-SUMMARY.md +478 -0
- package/LICENSE +21 -0
- package/README.md +482 -0
- package/capability-map.md +90 -0
- package/config/alert-rules.yml +269 -0
- package/config/prometheus.yml +136 -0
- package/dashboards/grafana-unrdf.json +798 -0
- package/dashboards/unrdf-workflow-dashboard.json +295 -0
- package/docs/OBSERVABILITY-PATTERNS.md +681 -0
- package/docs/OBSERVABILITY-RUNBOOK.md +554 -0
- package/examples/observability-demo.mjs +334 -0
- package/package.json +46 -0
- package/src/advanced-metrics.mjs +413 -0
- package/src/alerts/alert-manager.mjs +436 -0
- package/src/custom-events.mjs +558 -0
- package/src/distributed-tracing.mjs +352 -0
- package/src/exporters/grafana-exporter.mjs +415 -0
- package/src/index.mjs +61 -0
- package/src/metrics/workflow-metrics.mjs +346 -0
- package/src/receipts/anchor.mjs +155 -0
- package/src/receipts/index.mjs +62 -0
- package/src/receipts/merkle-tree.mjs +188 -0
- package/src/receipts/receipt-chain.mjs +209 -0
- package/src/receipts/receipt-schema.mjs +128 -0
- package/src/receipts/tamper-detection.mjs +219 -0
- package/test/advanced-metrics.test.mjs +302 -0
- package/test/custom-events.test.mjs +387 -0
- package/test/distributed-tracing.test.mjs +314 -0
- package/validation/observability-validation.mjs +366 -0
- package/vitest.config.mjs +25 -0
|
@@ -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
|
+
};
|