@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
package/src/reporter.mjs
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KGC Probe Reporter - Convert observations to RDF/Turtle and generate reports
|
|
3
|
+
*
|
|
4
|
+
* Transforms runtime observations into semantic RDF graphs and human-readable
|
|
5
|
+
* Markdown reports with derived capabilities and constraints.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createStore, dataFactory } from '@unrdf/oxigraph';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
|
|
12
|
+
// ===== Zod Schemas =====
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Schema for probe observation metadata
|
|
16
|
+
*/
|
|
17
|
+
const ObservationSchema = z.object({
|
|
18
|
+
method: z.string(),
|
|
19
|
+
domain: z.string().optional(),
|
|
20
|
+
timestamp: z.number().optional(),
|
|
21
|
+
outputs: z.any(),
|
|
22
|
+
error: z.string().optional(),
|
|
23
|
+
guardDecision: z.string().optional(),
|
|
24
|
+
hash: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Schema for derived capability claim
|
|
29
|
+
*/
|
|
30
|
+
const CapabilitySchema = z.object({
|
|
31
|
+
type: z.literal('capability'),
|
|
32
|
+
title: z.string(),
|
|
33
|
+
description: z.string(),
|
|
34
|
+
evidence: z.array(z.string()),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Schema for derived constraint claim
|
|
39
|
+
*/
|
|
40
|
+
const ConstraintSchema = z.object({
|
|
41
|
+
type: z.literal('constraint'),
|
|
42
|
+
title: z.string(),
|
|
43
|
+
description: z.string(),
|
|
44
|
+
evidence: z.array(z.string()),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ===== RDF Vocabulary Constants =====
|
|
48
|
+
|
|
49
|
+
const KGC_NS = 'http://unrdf.dev/kgc#';
|
|
50
|
+
const XSD_NS = 'http://www.w3.org/2001/XMLSchema#';
|
|
51
|
+
const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
|
|
52
|
+
const RDFS_NS = 'http://www.w3.org/2000/01/rdf-schema#';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a KGC namespace URI
|
|
56
|
+
* @param {string} localName - Local name in KGC namespace
|
|
57
|
+
* @returns {string} Full URI
|
|
58
|
+
*/
|
|
59
|
+
function kgcUri(localName) {
|
|
60
|
+
return `${KGC_NS}${localName}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate deterministic hash for observation
|
|
65
|
+
* @param {Object} observation - Observation object
|
|
66
|
+
* @returns {string} SHA-256 hash
|
|
67
|
+
*/
|
|
68
|
+
function generateHash(observation) {
|
|
69
|
+
if (observation.hash) return observation.hash;
|
|
70
|
+
|
|
71
|
+
const content = JSON.stringify({
|
|
72
|
+
method: observation.method,
|
|
73
|
+
outputs: observation.outputs,
|
|
74
|
+
timestamp: observation.timestamp,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convert observations array to RDF/Turtle format
|
|
82
|
+
*
|
|
83
|
+
* Each observation becomes a kgc:Observation resource with:
|
|
84
|
+
* - kgc:method (method name)
|
|
85
|
+
* - kgc:timestamp (ISO datetime)
|
|
86
|
+
* - kgc:hash (content hash for provenance)
|
|
87
|
+
* - kgc:outputs (JSON-serialized outputs)
|
|
88
|
+
* - kgc:guardDecision (if present)
|
|
89
|
+
* - kgc:error (if present)
|
|
90
|
+
* - kgc:domain (if present)
|
|
91
|
+
*
|
|
92
|
+
* @param {Array<Object>} observations - Array of observation objects
|
|
93
|
+
* @returns {string} Turtle-formatted RDF string
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const obs = [{ method: 'probeRuntime', outputs: { node: 'v18.19.0' } }];
|
|
97
|
+
* const turtle = observationsToRdf(obs);
|
|
98
|
+
* console.log(turtle); // @prefix kgc: <http://unrdf.dev/kgc#> . ...
|
|
99
|
+
*/
|
|
100
|
+
export function observationsToRdf(observations) {
|
|
101
|
+
// Validate input
|
|
102
|
+
const validatedObs = z.array(ObservationSchema).parse(observations);
|
|
103
|
+
|
|
104
|
+
// Sort observations by method name for deterministic output
|
|
105
|
+
const sortedObs = [...validatedObs].sort((a, b) =>
|
|
106
|
+
a.method.localeCompare(b.method)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Create RDF store
|
|
110
|
+
const store = createStore();
|
|
111
|
+
|
|
112
|
+
// Add observations as RDF quads
|
|
113
|
+
for (const obs of sortedObs) {
|
|
114
|
+
const hash = generateHash(obs);
|
|
115
|
+
const obsUri = dataFactory.namedNode(kgcUri(`observation/${hash}`));
|
|
116
|
+
const timestamp = obs.timestamp || Date.now();
|
|
117
|
+
|
|
118
|
+
// Type declaration
|
|
119
|
+
store.add(dataFactory.quad(
|
|
120
|
+
obsUri,
|
|
121
|
+
dataFactory.namedNode(`${RDF_NS}type`),
|
|
122
|
+
dataFactory.namedNode(kgcUri('Observation'))
|
|
123
|
+
));
|
|
124
|
+
|
|
125
|
+
// Method
|
|
126
|
+
store.add(dataFactory.quad(
|
|
127
|
+
obsUri,
|
|
128
|
+
dataFactory.namedNode(kgcUri('method')),
|
|
129
|
+
dataFactory.literal(obs.method)
|
|
130
|
+
));
|
|
131
|
+
|
|
132
|
+
// Timestamp (as xsd:dateTime)
|
|
133
|
+
const timestampDate = new Date(timestamp).toISOString();
|
|
134
|
+
store.add(dataFactory.quad(
|
|
135
|
+
obsUri,
|
|
136
|
+
dataFactory.namedNode(kgcUri('timestamp')),
|
|
137
|
+
dataFactory.literal(timestampDate, dataFactory.namedNode(`${XSD_NS}dateTime`))
|
|
138
|
+
));
|
|
139
|
+
|
|
140
|
+
// Hash
|
|
141
|
+
store.add(dataFactory.quad(
|
|
142
|
+
obsUri,
|
|
143
|
+
dataFactory.namedNode(kgcUri('hash')),
|
|
144
|
+
dataFactory.literal(hash, dataFactory.namedNode(`${XSD_NS}string`))
|
|
145
|
+
));
|
|
146
|
+
|
|
147
|
+
// Outputs (as JSON)
|
|
148
|
+
store.add(dataFactory.quad(
|
|
149
|
+
obsUri,
|
|
150
|
+
dataFactory.namedNode(kgcUri('outputs')),
|
|
151
|
+
dataFactory.literal(JSON.stringify(obs.outputs), dataFactory.namedNode(`${RDF_NS}JSON`))
|
|
152
|
+
));
|
|
153
|
+
|
|
154
|
+
// Optional: Guard decision
|
|
155
|
+
if (obs.guardDecision) {
|
|
156
|
+
store.add(dataFactory.quad(
|
|
157
|
+
obsUri,
|
|
158
|
+
dataFactory.namedNode(kgcUri('guardDecision')),
|
|
159
|
+
dataFactory.literal(obs.guardDecision, dataFactory.namedNode(`${XSD_NS}string`))
|
|
160
|
+
));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Optional: Error
|
|
164
|
+
if (obs.error) {
|
|
165
|
+
store.add(dataFactory.quad(
|
|
166
|
+
obsUri,
|
|
167
|
+
dataFactory.namedNode(kgcUri('error')),
|
|
168
|
+
dataFactory.literal(obs.error, dataFactory.namedNode(`${XSD_NS}string`))
|
|
169
|
+
));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Optional: Domain
|
|
173
|
+
if (obs.domain) {
|
|
174
|
+
store.add(dataFactory.quad(
|
|
175
|
+
obsUri,
|
|
176
|
+
dataFactory.namedNode(kgcUri('domain')),
|
|
177
|
+
dataFactory.literal(obs.domain, dataFactory.namedNode(`${XSD_NS}string`))
|
|
178
|
+
));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Serialize to Turtle
|
|
183
|
+
return store.dump({ format: 'turtle' });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Derive high-level capability and constraint claims from observations
|
|
188
|
+
*
|
|
189
|
+
* Analyzes observation patterns to extract:
|
|
190
|
+
* - Capabilities: Features/resources available in the environment
|
|
191
|
+
* - Constraints: Limitations or restrictions discovered
|
|
192
|
+
*
|
|
193
|
+
* @param {Array<Object>} observations - Array of observation objects
|
|
194
|
+
* @returns {Object} Object with capabilities and constraints arrays
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* const claims = deriveClaims(observations);
|
|
198
|
+
* console.log(claims.capabilities); // [{ type: 'capability', title: '...', ... }]
|
|
199
|
+
* console.log(claims.constraints); // [{ type: 'constraint', title: '...', ... }]
|
|
200
|
+
*/
|
|
201
|
+
export function deriveClaims(observations) {
|
|
202
|
+
// Validate input
|
|
203
|
+
const validatedObs = z.array(ObservationSchema).parse(observations);
|
|
204
|
+
|
|
205
|
+
const capabilities = [];
|
|
206
|
+
const constraints = [];
|
|
207
|
+
|
|
208
|
+
// Group observations by domain
|
|
209
|
+
const byDomain = new Map();
|
|
210
|
+
for (const obs of validatedObs) {
|
|
211
|
+
const domain = obs.domain || 'general';
|
|
212
|
+
if (!byDomain.has(domain)) {
|
|
213
|
+
byDomain.set(domain, []);
|
|
214
|
+
}
|
|
215
|
+
byDomain.get(domain).push(obs);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Analyze runtime observations
|
|
219
|
+
const runtimeObs = validatedObs.filter(o =>
|
|
220
|
+
o.method?.toLowerCase().includes('runtime') ||
|
|
221
|
+
o.domain === 'runtime'
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (runtimeObs.length > 0) {
|
|
225
|
+
const evidence = runtimeObs.map(o => o.method);
|
|
226
|
+
const outputs = runtimeObs.map(o => o.outputs).filter(Boolean);
|
|
227
|
+
|
|
228
|
+
// Extract Node.js version if present
|
|
229
|
+
const nodeVersion = outputs.find(o => o.node || o.version)?.node ||
|
|
230
|
+
outputs.find(o => o.node || o.version)?.version;
|
|
231
|
+
|
|
232
|
+
if (nodeVersion) {
|
|
233
|
+
capabilities.push({
|
|
234
|
+
type: 'capability',
|
|
235
|
+
title: `Node.js Runtime ${nodeVersion}`,
|
|
236
|
+
description: `Runtime environment running Node.js version ${nodeVersion}`,
|
|
237
|
+
evidence,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check for worker_threads
|
|
242
|
+
const workerThreads = outputs.some(o =>
|
|
243
|
+
o.worker_threads === true ||
|
|
244
|
+
o.workerThreads === true ||
|
|
245
|
+
(typeof o === 'object' && 'worker_threads' in o)
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
if (workerThreads) {
|
|
249
|
+
capabilities.push({
|
|
250
|
+
type: 'capability',
|
|
251
|
+
title: 'Worker Threads Available',
|
|
252
|
+
description: 'Runtime supports worker_threads for parallel execution',
|
|
253
|
+
evidence,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Analyze WASM observations
|
|
259
|
+
const wasmObs = validatedObs.filter(o =>
|
|
260
|
+
o.method?.toLowerCase().includes('wasm') ||
|
|
261
|
+
o.domain === 'wasm'
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (wasmObs.length > 0) {
|
|
265
|
+
const evidence = wasmObs.map(o => o.method);
|
|
266
|
+
const outputs = wasmObs.map(o => o.outputs).filter(Boolean);
|
|
267
|
+
|
|
268
|
+
const wasmAvailable = outputs.some(o => o.available === true || o.wasm === true);
|
|
269
|
+
|
|
270
|
+
if (wasmAvailable) {
|
|
271
|
+
const maxMemory = outputs.find(o => o.maxMemory)?.maxMemory;
|
|
272
|
+
const memoryDesc = maxMemory ? ` with maximum memory ${maxMemory}` : '';
|
|
273
|
+
|
|
274
|
+
capabilities.push({
|
|
275
|
+
type: 'capability',
|
|
276
|
+
title: 'WebAssembly Support',
|
|
277
|
+
description: `WASM compilation and execution available${memoryDesc}`,
|
|
278
|
+
evidence,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Analyze filesystem observations
|
|
284
|
+
const fsObs = validatedObs.filter(o =>
|
|
285
|
+
o.method?.toLowerCase().includes('filesystem') ||
|
|
286
|
+
o.method?.toLowerCase().includes('fs') ||
|
|
287
|
+
o.domain === 'filesystem'
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (fsObs.length > 0) {
|
|
291
|
+
const evidence = fsObs.map(o => o.method);
|
|
292
|
+
const outputs = fsObs.map(o => o.outputs).filter(Boolean);
|
|
293
|
+
|
|
294
|
+
// Check for restricted paths
|
|
295
|
+
const allowedPaths = outputs.find(o => o.allowedPaths || o.paths)?.allowedPaths ||
|
|
296
|
+
outputs.find(o => o.allowedPaths || o.paths)?.paths;
|
|
297
|
+
|
|
298
|
+
if (allowedPaths && Array.isArray(allowedPaths)) {
|
|
299
|
+
constraints.push({
|
|
300
|
+
type: 'constraint',
|
|
301
|
+
title: 'Filesystem Access Restricted',
|
|
302
|
+
description: `Filesystem access limited to: ${allowedPaths.join(', ')}`,
|
|
303
|
+
evidence,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Check for denied operations
|
|
308
|
+
const deniedOps = fsObs.filter(o =>
|
|
309
|
+
o.guardDecision === 'denied' ||
|
|
310
|
+
o.error?.includes('permission') ||
|
|
311
|
+
o.error?.includes('denied')
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
if (deniedOps.length > 0) {
|
|
315
|
+
constraints.push({
|
|
316
|
+
type: 'constraint',
|
|
317
|
+
title: 'Filesystem Operations Denied',
|
|
318
|
+
description: `${deniedOps.length} filesystem operation(s) were denied by security guards`,
|
|
319
|
+
evidence: deniedOps.map(o => o.method),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Analyze network observations
|
|
325
|
+
const networkObs = validatedObs.filter(o =>
|
|
326
|
+
o.method?.toLowerCase().includes('network') ||
|
|
327
|
+
o.domain === 'network'
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
if (networkObs.length > 0) {
|
|
331
|
+
const evidence = networkObs.map(o => o.method);
|
|
332
|
+
const outputs = networkObs.map(o => o.outputs).filter(Boolean);
|
|
333
|
+
|
|
334
|
+
// Check for allowlisted URLs
|
|
335
|
+
const allowedUrls = outputs.find(o => o.allowedUrls || o.urls)?.allowedUrls ||
|
|
336
|
+
outputs.find(o => o.allowedUrls || o.urls)?.urls;
|
|
337
|
+
|
|
338
|
+
if (allowedUrls && Array.isArray(allowedUrls)) {
|
|
339
|
+
constraints.push({
|
|
340
|
+
type: 'constraint',
|
|
341
|
+
title: 'Network Access Allowlist',
|
|
342
|
+
description: `Network requests restricted to ${allowedUrls.length} allowlisted URL(s)`,
|
|
343
|
+
evidence,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Check for denied requests
|
|
348
|
+
const deniedReqs = networkObs.filter(o =>
|
|
349
|
+
o.guardDecision === 'denied' ||
|
|
350
|
+
o.error?.includes('network') ||
|
|
351
|
+
o.error?.includes('blocked')
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
if (deniedReqs.length > 0) {
|
|
355
|
+
constraints.push({
|
|
356
|
+
type: 'constraint',
|
|
357
|
+
title: 'Network Requests Blocked',
|
|
358
|
+
description: `${deniedReqs.length} network request(s) were blocked`,
|
|
359
|
+
evidence: deniedReqs.map(o => o.method),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Analyze performance observations
|
|
365
|
+
const perfObs = validatedObs.filter(o =>
|
|
366
|
+
o.method?.toLowerCase().includes('performance') ||
|
|
367
|
+
o.method?.toLowerCase().includes('benchmark') ||
|
|
368
|
+
o.domain === 'performance'
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
if (perfObs.length > 0) {
|
|
372
|
+
const evidence = perfObs.map(o => o.method);
|
|
373
|
+
const outputs = perfObs.map(o => o.outputs).filter(Boolean);
|
|
374
|
+
|
|
375
|
+
// Look for execution times
|
|
376
|
+
const executionTimes = outputs
|
|
377
|
+
.map(o => o.executionTime || o.duration || o.time)
|
|
378
|
+
.filter(t => typeof t === 'number');
|
|
379
|
+
|
|
380
|
+
if (executionTimes.length > 0) {
|
|
381
|
+
const avgTime = executionTimes.reduce((a, b) => a + b, 0) / executionTimes.length;
|
|
382
|
+
capabilities.push({
|
|
383
|
+
type: 'capability',
|
|
384
|
+
title: 'Performance Metrics',
|
|
385
|
+
description: `Average execution time: ${avgTime.toFixed(2)}ms across ${executionTimes.length} measurement(s)`,
|
|
386
|
+
evidence,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Validate outputs
|
|
392
|
+
const validatedCapabilities = capabilities.map(c => CapabilitySchema.parse(c));
|
|
393
|
+
const validatedConstraints = constraints.map(c => ConstraintSchema.parse(c));
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
capabilities: validatedCapabilities,
|
|
397
|
+
constraints: validatedConstraints,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Generate comprehensive Markdown report from observations
|
|
403
|
+
*
|
|
404
|
+
* Creates a structured report with:
|
|
405
|
+
* - Executive summary with counts
|
|
406
|
+
* - Discovered capabilities
|
|
407
|
+
* - Detected constraints
|
|
408
|
+
* - Observations grouped by domain
|
|
409
|
+
*
|
|
410
|
+
* @param {Array<Object>} observations - Array of observation objects
|
|
411
|
+
* @returns {string} Markdown-formatted report
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* const report = generateReport(observations);
|
|
415
|
+
* console.log(report); // # KGC Probe Report\n\n## Summary\n...
|
|
416
|
+
*/
|
|
417
|
+
export function generateReport(observations) {
|
|
418
|
+
// Validate input
|
|
419
|
+
const validatedObs = z.array(ObservationSchema).parse(observations);
|
|
420
|
+
|
|
421
|
+
// Derive claims
|
|
422
|
+
const { capabilities, constraints } = deriveClaims(validatedObs);
|
|
423
|
+
|
|
424
|
+
// Calculate execution time range
|
|
425
|
+
const timestamps = validatedObs
|
|
426
|
+
.map(o => o.timestamp)
|
|
427
|
+
.filter(t => typeof t === 'number')
|
|
428
|
+
.sort((a, b) => a - b);
|
|
429
|
+
|
|
430
|
+
const executionTime = timestamps.length >= 2
|
|
431
|
+
? timestamps[timestamps.length - 1] - timestamps[0]
|
|
432
|
+
: 0;
|
|
433
|
+
|
|
434
|
+
// Group observations by domain
|
|
435
|
+
const byDomain = new Map();
|
|
436
|
+
for (const obs of validatedObs) {
|
|
437
|
+
const domain = obs.domain || 'general';
|
|
438
|
+
if (!byDomain.has(domain)) {
|
|
439
|
+
byDomain.set(domain, []);
|
|
440
|
+
}
|
|
441
|
+
byDomain.get(domain).push(obs);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Sort domains alphabetically
|
|
445
|
+
const sortedDomains = Array.from(byDomain.keys()).sort();
|
|
446
|
+
|
|
447
|
+
// Build report
|
|
448
|
+
let report = '# KGC Probe Report\n\n';
|
|
449
|
+
|
|
450
|
+
// Summary section
|
|
451
|
+
report += '## Summary\n\n';
|
|
452
|
+
report += `- **Total observations**: ${validatedObs.length}\n`;
|
|
453
|
+
report += `- **Capabilities discovered**: ${capabilities.length}\n`;
|
|
454
|
+
report += `- **Constraints detected**: ${constraints.length}\n`;
|
|
455
|
+
report += `- **Execution time**: ${executionTime}ms\n`;
|
|
456
|
+
report += `- **Domains probed**: ${sortedDomains.length}\n`;
|
|
457
|
+
report += '\n';
|
|
458
|
+
|
|
459
|
+
// Capabilities section
|
|
460
|
+
if (capabilities.length > 0) {
|
|
461
|
+
report += '## Capabilities\n\n';
|
|
462
|
+
report += 'The following capabilities were discovered in the runtime environment:\n\n';
|
|
463
|
+
|
|
464
|
+
for (const capability of capabilities) {
|
|
465
|
+
report += `### ${capability.title}\n\n`;
|
|
466
|
+
report += `${capability.description}\n\n`;
|
|
467
|
+
report += `**Evidence**: ${capability.evidence.join(', ')}\n\n`;
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
report += '## Capabilities\n\n';
|
|
471
|
+
report += '*No capabilities detected*\n\n';
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Constraints section
|
|
475
|
+
if (constraints.length > 0) {
|
|
476
|
+
report += '## Constraints\n\n';
|
|
477
|
+
report += 'The following constraints and limitations were detected:\n\n';
|
|
478
|
+
|
|
479
|
+
for (const constraint of constraints) {
|
|
480
|
+
report += `### ${constraint.title}\n\n`;
|
|
481
|
+
report += `${constraint.description}\n\n`;
|
|
482
|
+
report += `**Evidence**: ${constraint.evidence.join(', ')}\n\n`;
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
report += '## Constraints\n\n';
|
|
486
|
+
report += '*No constraints detected*\n\n';
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Observations by domain
|
|
490
|
+
report += '## Observations by Domain\n\n';
|
|
491
|
+
|
|
492
|
+
for (const domain of sortedDomains) {
|
|
493
|
+
const domainObs = byDomain.get(domain);
|
|
494
|
+
report += `### ${domain.charAt(0).toUpperCase() + domain.slice(1)} (${domainObs.length} observations)\n\n`;
|
|
495
|
+
|
|
496
|
+
// Sort observations by method name
|
|
497
|
+
const sortedDomainObs = [...domainObs].sort((a, b) =>
|
|
498
|
+
a.method.localeCompare(b.method)
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
for (const obs of sortedDomainObs) {
|
|
502
|
+
report += `#### ${obs.method}\n\n`;
|
|
503
|
+
|
|
504
|
+
// Guard decision
|
|
505
|
+
if (obs.guardDecision) {
|
|
506
|
+
report += `**Guard Decision**: ${obs.guardDecision}\n\n`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Outputs
|
|
510
|
+
if (obs.outputs) {
|
|
511
|
+
report += '**Outputs**:\n```json\n';
|
|
512
|
+
report += JSON.stringify(obs.outputs, null, 2);
|
|
513
|
+
report += '\n```\n\n';
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Error
|
|
517
|
+
if (obs.error) {
|
|
518
|
+
report += `**Error**: ${obs.error}\n\n`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Timestamp
|
|
522
|
+
if (obs.timestamp) {
|
|
523
|
+
const timestampDate = new Date(obs.timestamp).toISOString();
|
|
524
|
+
report += `**Timestamp**: ${timestampDate}\n\n`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Hash
|
|
528
|
+
const hash = generateHash(obs);
|
|
529
|
+
report += `**Hash**: ${hash}\n\n`;
|
|
530
|
+
|
|
531
|
+
report += '---\n\n';
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Footer
|
|
536
|
+
report += `\n*Report generated at ${new Date().toISOString()}*\n`;
|
|
537
|
+
|
|
538
|
+
return report;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Export schemas for external use
|
|
542
|
+
export {
|
|
543
|
+
ObservationSchema,
|
|
544
|
+
CapabilitySchema,
|
|
545
|
+
ConstraintSchema,
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
export default {
|
|
549
|
+
observationsToRdf,
|
|
550
|
+
generateReport,
|
|
551
|
+
deriveClaims,
|
|
552
|
+
ObservationSchema,
|
|
553
|
+
CapabilitySchema,
|
|
554
|
+
ConstraintSchema,
|
|
555
|
+
};
|