@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.
- package/README.md +414 -0
- package/package.json +81 -0
- package/src/agents/index.mjs +1402 -0
- package/src/artifact.mjs +405 -0
- package/src/cli.mjs +932 -0
- package/src/config.mjs +115 -0
- package/src/guards.mjs +1213 -0
- package/src/index.mjs +347 -0
- package/src/merge.mjs +196 -0
- package/src/observation.mjs +193 -0
- package/src/orchestrator.mjs +315 -0
- package/src/probe.mjs +58 -0
- package/src/probes/CONCURRENCY-PROBE.md +256 -0
- package/src/probes/README.md +275 -0
- package/src/probes/concurrency.mjs +1175 -0
- package/src/probes/filesystem.mjs +731 -0
- package/src/probes/filesystem.test.mjs +244 -0
- package/src/probes/network.mjs +503 -0
- package/src/probes/performance.mjs +816 -0
- package/src/probes/persistence.mjs +785 -0
- package/src/probes/runtime.mjs +589 -0
- package/src/probes/tooling.mjs +454 -0
- package/src/probes/tooling.test.mjs +372 -0
- package/src/probes/verify-execution.mjs +131 -0
- package/src/probes/verify-guards.mjs +73 -0
- package/src/probes/wasm.mjs +715 -0
- package/src/receipt.mjs +197 -0
- package/src/receipts/index.mjs +813 -0
- package/src/reporter.example.mjs +223 -0
- package/src/reporter.mjs +555 -0
- package/src/reporters/markdown.mjs +355 -0
- package/src/reporters/rdf.mjs +383 -0
- package/src/storage/index.mjs +827 -0
- package/src/types.mjs +1028 -0
- package/src/utils/errors.mjs +397 -0
- package/src/utils/index.mjs +32 -0
- package/src/utils/logger.mjs +236 -0
- package/src/vocabulary.ttl +169 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Markdown reporter for KGC Probe observations
|
|
3
|
+
*
|
|
4
|
+
* Generates human-readable reports from observations with:
|
|
5
|
+
* - Executive summary (platform, runtime, key capabilities)
|
|
6
|
+
* - Capabilities by domain (grouped)
|
|
7
|
+
* - Constraints by domain (grouped)
|
|
8
|
+
* - Performance metrics (tables)
|
|
9
|
+
* - Guard denials (if any)
|
|
10
|
+
* - Provenance (observation count, hash chain)
|
|
11
|
+
*
|
|
12
|
+
* Design principles:
|
|
13
|
+
* - Human-readable: Clear structure, visual hierarchy
|
|
14
|
+
* - Comprehensive: All relevant information included
|
|
15
|
+
* - Actionable: Highlights issues and limitations
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import crypto from 'crypto';
|
|
19
|
+
import { deriveCapabilities, deriveConstraints } from './rdf.mjs';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate deterministic hash for observation
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} observation - Observation object
|
|
25
|
+
* @returns {string} SHA-256 hash (first 16 chars)
|
|
26
|
+
*/
|
|
27
|
+
function generateHash(observation) {
|
|
28
|
+
if (observation.hash) return observation.hash;
|
|
29
|
+
if (observation.receiptHash) return observation.receiptHash;
|
|
30
|
+
|
|
31
|
+
const content = JSON.stringify({
|
|
32
|
+
method: observation.method,
|
|
33
|
+
category: observation.category,
|
|
34
|
+
message: observation.message,
|
|
35
|
+
outputs: observation.outputs || observation.data,
|
|
36
|
+
timestamp: observation.timestamp || observation.metadata?.timestamp,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract platform information from observations
|
|
44
|
+
*
|
|
45
|
+
* @param {Array<Object>} observations - Array of observations
|
|
46
|
+
* @returns {Object} Platform info
|
|
47
|
+
*/
|
|
48
|
+
function extractPlatformInfo(observations) {
|
|
49
|
+
const info = {
|
|
50
|
+
platform: 'unknown',
|
|
51
|
+
runtime: 'unknown',
|
|
52
|
+
arch: 'unknown',
|
|
53
|
+
nodeVersion: 'unknown'
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
for (const obs of observations) {
|
|
57
|
+
const data = obs.outputs || obs.data || {};
|
|
58
|
+
|
|
59
|
+
// Extract Node.js version
|
|
60
|
+
if (data.nodeVersion) {
|
|
61
|
+
info.nodeVersion = data.nodeVersion;
|
|
62
|
+
info.runtime = `Node.js ${data.nodeVersion}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Extract platform/arch
|
|
66
|
+
if (data.platform) info.platform = data.platform;
|
|
67
|
+
if (data.arch) info.arch = data.arch;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return info;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Group observations by domain
|
|
75
|
+
*
|
|
76
|
+
* @param {Array<Object>} observations - Array of observations
|
|
77
|
+
* @returns {Map<string, Array<Object>>} Observations grouped by domain
|
|
78
|
+
*/
|
|
79
|
+
function groupByDomain(observations) {
|
|
80
|
+
const byDomain = new Map();
|
|
81
|
+
|
|
82
|
+
for (const obs of observations) {
|
|
83
|
+
const domain = obs.domain || obs.category || 'general';
|
|
84
|
+
if (!byDomain.has(domain)) {
|
|
85
|
+
byDomain.set(domain, []);
|
|
86
|
+
}
|
|
87
|
+
byDomain.get(domain).push(obs);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return byDomain;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Extract performance metrics from observations
|
|
95
|
+
*
|
|
96
|
+
* @param {Array<Object>} observations - Array of observations
|
|
97
|
+
* @returns {Array<Object>} Performance metrics
|
|
98
|
+
*/
|
|
99
|
+
function extractPerformanceMetrics(observations) {
|
|
100
|
+
const metrics = [];
|
|
101
|
+
|
|
102
|
+
for (const obs of observations) {
|
|
103
|
+
const data = obs.outputs || obs.data || {};
|
|
104
|
+
|
|
105
|
+
// Check for performance data
|
|
106
|
+
if (data.mean !== undefined || data.median !== undefined || data.p95 !== undefined) {
|
|
107
|
+
metrics.push({
|
|
108
|
+
method: obs.method || obs.message || 'unknown',
|
|
109
|
+
mean: data.mean,
|
|
110
|
+
median: data.median,
|
|
111
|
+
p95: data.p95,
|
|
112
|
+
min: data.min,
|
|
113
|
+
max: data.max,
|
|
114
|
+
unit: data.unit || 'ms',
|
|
115
|
+
samples: data.samples || 1
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check for throughput data
|
|
120
|
+
if (data.throughputMBps !== undefined) {
|
|
121
|
+
metrics.push({
|
|
122
|
+
method: obs.method || obs.message || 'unknown',
|
|
123
|
+
throughput: data.throughputMBps,
|
|
124
|
+
unit: 'MB/s',
|
|
125
|
+
samples: data.samples || 1
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return metrics;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Extract guard denials from observations
|
|
135
|
+
*
|
|
136
|
+
* @param {Array<Object>} observations - Array of observations
|
|
137
|
+
* @returns {Array<Object>} Guard denials
|
|
138
|
+
*/
|
|
139
|
+
function extractGuardDenials(observations) {
|
|
140
|
+
const denials = [];
|
|
141
|
+
|
|
142
|
+
for (const obs of observations) {
|
|
143
|
+
const data = obs.outputs || obs.data || {};
|
|
144
|
+
|
|
145
|
+
if (obs.guardDecision === 'denied' || obs.category === 'guard' || data.guardDecision === 'denied') {
|
|
146
|
+
denials.push({
|
|
147
|
+
method: obs.method || obs.message || 'unknown',
|
|
148
|
+
reason: data.reason || obs.message || 'Access denied',
|
|
149
|
+
guard: data.guardName || 'unknown',
|
|
150
|
+
hash: generateHash(obs)
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return denials;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Calculate hash chain from observations
|
|
160
|
+
*
|
|
161
|
+
* @param {Array<Object>} observations - Array of observations
|
|
162
|
+
* @returns {string} Chain hash
|
|
163
|
+
*/
|
|
164
|
+
function calculateChainHash(observations) {
|
|
165
|
+
const hashes = observations.map(obs => generateHash(obs)).sort();
|
|
166
|
+
const content = hashes.join('');
|
|
167
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Render report as Markdown
|
|
172
|
+
*
|
|
173
|
+
* @param {Array<Object>} observations - Array of observation objects
|
|
174
|
+
* @returns {string} Markdown-formatted report
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* const report = renderReport(observations);
|
|
178
|
+
* console.log(report); // # KGC Probe Report\n\n## Executive Summary\n...
|
|
179
|
+
*/
|
|
180
|
+
export function renderReport(observations) {
|
|
181
|
+
// Sort observations for deterministic output
|
|
182
|
+
const sortedObs = [...observations].sort((a, b) => {
|
|
183
|
+
const hashA = generateHash(a);
|
|
184
|
+
const hashB = generateHash(b);
|
|
185
|
+
return hashA.localeCompare(hashB);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Extract components
|
|
189
|
+
const platform = extractPlatformInfo(sortedObs);
|
|
190
|
+
const capabilities = deriveCapabilities(sortedObs);
|
|
191
|
+
const constraints = deriveConstraints(sortedObs);
|
|
192
|
+
const byDomain = groupByDomain(sortedObs);
|
|
193
|
+
const perfMetrics = extractPerformanceMetrics(sortedObs);
|
|
194
|
+
const guardDenials = extractGuardDenials(sortedObs);
|
|
195
|
+
const chainHash = calculateChainHash(sortedObs);
|
|
196
|
+
|
|
197
|
+
// Calculate run ID from first timestamp
|
|
198
|
+
const firstTimestamp = sortedObs.length > 0
|
|
199
|
+
? (sortedObs[0].timestamp || sortedObs[0].metadata?.timestamp || Date.now())
|
|
200
|
+
: Date.now();
|
|
201
|
+
const runDate = typeof firstTimestamp === 'number'
|
|
202
|
+
? new Date(firstTimestamp).toISOString()
|
|
203
|
+
: firstTimestamp;
|
|
204
|
+
const runId = `run_${runDate.replace(/[:.]/g, '-').substring(0, 23)}`;
|
|
205
|
+
|
|
206
|
+
// Build Markdown report
|
|
207
|
+
let report = '# KGC Probe Report\n\n';
|
|
208
|
+
|
|
209
|
+
// Metadata header
|
|
210
|
+
report += `**Run ID**: ${runId} \n`;
|
|
211
|
+
report += `**Observations**: ${sortedObs.length} \n`;
|
|
212
|
+
report += `**Hash**: sha256:${chainHash}\n\n`;
|
|
213
|
+
|
|
214
|
+
// Executive Summary
|
|
215
|
+
report += '## Executive Summary\n\n';
|
|
216
|
+
report += `- **Platform**: ${platform.platform} ${platform.arch}\n`;
|
|
217
|
+
report += `- **Runtime**: ${platform.runtime}\n`;
|
|
218
|
+
|
|
219
|
+
// Key capabilities summary
|
|
220
|
+
const wasmCap = capabilities.find(c => c.name.includes('wasm'));
|
|
221
|
+
const workerCap = capabilities.find(c => c.name.includes('worker'));
|
|
222
|
+
|
|
223
|
+
report += `- **WASM**: ${wasmCap ? 'Available' : 'Not detected'}\n`;
|
|
224
|
+
report += `- **Workers**: ${workerCap ? 'Available' : 'Not detected'}\n`;
|
|
225
|
+
report += `- **Capabilities**: ${capabilities.length} discovered\n`;
|
|
226
|
+
report += `- **Constraints**: ${constraints.length} detected\n`;
|
|
227
|
+
report += '\n';
|
|
228
|
+
|
|
229
|
+
// Capabilities Section
|
|
230
|
+
report += '## Capabilities\n\n';
|
|
231
|
+
|
|
232
|
+
if (capabilities.length > 0) {
|
|
233
|
+
// Group capabilities by domain
|
|
234
|
+
const capByDomain = new Map();
|
|
235
|
+
for (const cap of capabilities) {
|
|
236
|
+
const domain = cap.name.split('.')[0] || 'general';
|
|
237
|
+
if (!capByDomain.has(domain)) {
|
|
238
|
+
capByDomain.set(domain, []);
|
|
239
|
+
}
|
|
240
|
+
capByDomain.get(domain).push(cap);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Sort domains alphabetically
|
|
244
|
+
const sortedDomains = Array.from(capByDomain.keys()).sort();
|
|
245
|
+
|
|
246
|
+
for (const domain of sortedDomains) {
|
|
247
|
+
report += `### ${domain.charAt(0).toUpperCase() + domain.slice(1)}\n\n`;
|
|
248
|
+
|
|
249
|
+
const domainCaps = capByDomain.get(domain);
|
|
250
|
+
for (const cap of domainCaps) {
|
|
251
|
+
report += `- ✅ **${cap.name}**\n`;
|
|
252
|
+
|
|
253
|
+
if (cap.data && Object.keys(cap.data).length > 0) {
|
|
254
|
+
const dataStr = JSON.stringify(cap.data, null, 2);
|
|
255
|
+
report += ` \`\`\`json\n ${dataStr.split('\n').join('\n ')}\n \`\`\`\n`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
report += '\n';
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
report += '*No capabilities detected*\n\n';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Constraints Section
|
|
265
|
+
report += '## Constraints\n\n';
|
|
266
|
+
|
|
267
|
+
if (constraints.length > 0) {
|
|
268
|
+
// Group constraints by type
|
|
269
|
+
const constraintByType = new Map();
|
|
270
|
+
for (const constraint of constraints) {
|
|
271
|
+
const type = constraint.type || 'general';
|
|
272
|
+
if (!constraintByType.has(type)) {
|
|
273
|
+
constraintByType.set(type, []);
|
|
274
|
+
}
|
|
275
|
+
constraintByType.get(type).push(constraint);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Sort types alphabetically
|
|
279
|
+
const sortedTypes = Array.from(constraintByType.keys()).sort();
|
|
280
|
+
|
|
281
|
+
for (const type of sortedTypes) {
|
|
282
|
+
report += `### ${type.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}\n\n`;
|
|
283
|
+
|
|
284
|
+
const typeConstraints = constraintByType.get(type);
|
|
285
|
+
for (const constraint of typeConstraints) {
|
|
286
|
+
report += `- ⚠️ ${constraint.description}\n`;
|
|
287
|
+
}
|
|
288
|
+
report += '\n';
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
report += '*No constraints detected*\n\n';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Performance Metrics Section
|
|
295
|
+
if (perfMetrics.length > 0) {
|
|
296
|
+
report += '## Performance\n\n';
|
|
297
|
+
report += '| Method | Mean | Median | P95 | Min | Max | Unit | Samples |\n';
|
|
298
|
+
report += '|--------|------|--------|-----|-----|-----|------|--------|\n';
|
|
299
|
+
|
|
300
|
+
for (const metric of perfMetrics) {
|
|
301
|
+
const mean = metric.mean !== undefined ? metric.mean.toFixed(2) : '-';
|
|
302
|
+
const median = metric.median !== undefined ? metric.median.toFixed(2) : '-';
|
|
303
|
+
const p95 = metric.p95 !== undefined ? metric.p95.toFixed(2) : '-';
|
|
304
|
+
const min = metric.min !== undefined ? metric.min.toFixed(2) : '-';
|
|
305
|
+
const max = metric.max !== undefined ? metric.max.toFixed(2) : '-';
|
|
306
|
+
const throughput = metric.throughput !== undefined ? metric.throughput.toFixed(2) : '-';
|
|
307
|
+
|
|
308
|
+
if (metric.throughput !== undefined) {
|
|
309
|
+
report += `| ${metric.method} | ${throughput} | - | - | - | - | ${metric.unit} | ${metric.samples} |\n`;
|
|
310
|
+
} else {
|
|
311
|
+
report += `| ${metric.method} | ${mean} | ${median} | ${p95} | ${min} | ${max} | ${metric.unit} | ${metric.samples} |\n`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
report += '\n';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Guard Denials Section
|
|
318
|
+
if (guardDenials.length > 0) {
|
|
319
|
+
report += '## Guard Denials\n\n';
|
|
320
|
+
report += `${guardDenials.length} operation(s) were denied by security guards:\n\n`;
|
|
321
|
+
|
|
322
|
+
for (const denial of guardDenials) {
|
|
323
|
+
report += `- **${denial.method}**\n`;
|
|
324
|
+
report += ` - Guard: \`${denial.guard}\`\n`;
|
|
325
|
+
report += ` - Reason: ${denial.reason}\n`;
|
|
326
|
+
report += ` - Hash: \`${denial.hash}\`\n\n`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Provenance Section
|
|
331
|
+
report += '## Provenance\n\n';
|
|
332
|
+
report += `- **Observation count**: ${sortedObs.length}\n`;
|
|
333
|
+
report += `- **Hash chain**: \`sha256:${chainHash}\`\n`;
|
|
334
|
+
report += `- **Domains probed**: ${byDomain.size}\n`;
|
|
335
|
+
|
|
336
|
+
const domainCounts = Array.from(byDomain.entries())
|
|
337
|
+
.map(([domain, obs]) => ` - ${domain}: ${obs.length}`)
|
|
338
|
+
.join('\n');
|
|
339
|
+
|
|
340
|
+
report += '\n**Observations by domain**:\n';
|
|
341
|
+
report += domainCounts + '\n\n';
|
|
342
|
+
|
|
343
|
+
// Footer
|
|
344
|
+
report += `---\n\n`;
|
|
345
|
+
report += `*Report generated at ${new Date().toISOString()}*\n`;
|
|
346
|
+
|
|
347
|
+
return report;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export default {
|
|
351
|
+
renderReport,
|
|
352
|
+
extractPlatformInfo,
|
|
353
|
+
extractPerformanceMetrics,
|
|
354
|
+
extractGuardDenials
|
|
355
|
+
};
|