@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,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview RDF/Turtle converter for KGC Probe observations
|
|
3
|
+
*
|
|
4
|
+
* Converts observations to RDF/Turtle format with capability and constraint derivation.
|
|
5
|
+
* Uses the observation schema from orchestrator (category, severity, message format).
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* - Deterministic: Same observations → same Turtle output
|
|
9
|
+
* - Provenance: Links capabilities/constraints to observation hashes
|
|
10
|
+
* - Minimal vocabulary: kgc:Observation, kgc:Capability, kgc:Constraint
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createStore, dataFactory } from '@unrdf/oxigraph';
|
|
14
|
+
import crypto from 'crypto';
|
|
15
|
+
|
|
16
|
+
// ===== RDF Vocabulary Constants =====
|
|
17
|
+
|
|
18
|
+
const KGC_NS = 'https://unrdf.org/kgc/probe#';
|
|
19
|
+
const XSD_NS = 'http://www.w3.org/2001/XMLSchema#';
|
|
20
|
+
const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
|
|
21
|
+
const RDFS_NS = 'http://www.w3.org/2000/01/rdf-schema#';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a KGC namespace URI
|
|
25
|
+
*
|
|
26
|
+
* @param {string} localName - Local name in KGC namespace
|
|
27
|
+
* @returns {string} Full URI
|
|
28
|
+
*/
|
|
29
|
+
function kgcUri(localName) {
|
|
30
|
+
return `${KGC_NS}${localName}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate deterministic hash for observation
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} observation - Observation object
|
|
37
|
+
* @returns {string} SHA-256 hash (first 16 chars)
|
|
38
|
+
*/
|
|
39
|
+
function generateHash(observation) {
|
|
40
|
+
if (observation.hash) return observation.hash;
|
|
41
|
+
if (observation.receiptHash) return observation.receiptHash;
|
|
42
|
+
|
|
43
|
+
const content = JSON.stringify({
|
|
44
|
+
method: observation.method,
|
|
45
|
+
category: observation.category,
|
|
46
|
+
message: observation.message,
|
|
47
|
+
outputs: observation.outputs || observation.data,
|
|
48
|
+
timestamp: observation.timestamp || observation.metadata?.timestamp,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Derive capabilities from observations
|
|
56
|
+
*
|
|
57
|
+
* Capability = feature/resource available in the environment
|
|
58
|
+
* Derives when:
|
|
59
|
+
* - observation.outputs.available === true
|
|
60
|
+
* - observation.data indicates successful feature detection
|
|
61
|
+
* - No error present
|
|
62
|
+
*
|
|
63
|
+
* @param {Array<Object>} observations - Array of observations
|
|
64
|
+
* @returns {Array<Object>} Capabilities with provenance
|
|
65
|
+
*/
|
|
66
|
+
function deriveCapabilities(observations) {
|
|
67
|
+
const capabilities = [];
|
|
68
|
+
|
|
69
|
+
// Group by domain/method for analysis
|
|
70
|
+
for (const obs of observations) {
|
|
71
|
+
const hash = generateHash(obs);
|
|
72
|
+
const data = obs.outputs || obs.data || {};
|
|
73
|
+
|
|
74
|
+
// Check for explicit availability flag
|
|
75
|
+
if (data.available === true) {
|
|
76
|
+
const domain = obs.domain || obs.category || 'unknown';
|
|
77
|
+
const method = obs.method || obs.message || 'unknown';
|
|
78
|
+
|
|
79
|
+
capabilities.push({
|
|
80
|
+
name: `${domain}.${method}`,
|
|
81
|
+
available: true,
|
|
82
|
+
derivedFrom: [`urn:kgc:obs:${hash}`],
|
|
83
|
+
data
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check for specific capability indicators
|
|
88
|
+
if (data.worker_threads === true || data.workerThreads === true) {
|
|
89
|
+
capabilities.push({
|
|
90
|
+
name: 'concurrency.worker_threads',
|
|
91
|
+
available: true,
|
|
92
|
+
derivedFrom: [`urn:kgc:obs:${hash}`],
|
|
93
|
+
data: { module: 'worker_threads' }
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (data.wasm === true || data.WebAssembly === true) {
|
|
98
|
+
capabilities.push({
|
|
99
|
+
name: 'runtime.wasm',
|
|
100
|
+
available: true,
|
|
101
|
+
derivedFrom: [`urn:kgc:obs:${hash}`],
|
|
102
|
+
data: { support: 'available' }
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return capabilities;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Derive constraints from observations
|
|
112
|
+
*
|
|
113
|
+
* Constraint = limitation/boundary discovered in the environment
|
|
114
|
+
* Derives when:
|
|
115
|
+
* - observation shows guard denial
|
|
116
|
+
* - observation shows error/limit
|
|
117
|
+
* - observation shows restricted access
|
|
118
|
+
*
|
|
119
|
+
* @param {Array<Object>} observations - Array of observations
|
|
120
|
+
* @returns {Array<Object>} Constraints with provenance
|
|
121
|
+
*/
|
|
122
|
+
function deriveConstraints(observations) {
|
|
123
|
+
const constraints = [];
|
|
124
|
+
|
|
125
|
+
for (const obs of observations) {
|
|
126
|
+
const hash = generateHash(obs);
|
|
127
|
+
const data = obs.outputs || obs.data || {};
|
|
128
|
+
|
|
129
|
+
// Check for guard denials
|
|
130
|
+
if (obs.guardDecision === 'denied' || obs.category === 'guard' || data.guardDecision === 'denied') {
|
|
131
|
+
constraints.push({
|
|
132
|
+
type: 'guard-denial',
|
|
133
|
+
description: obs.message || 'Operation denied by guard',
|
|
134
|
+
derivedFrom: [`urn:kgc:obs:${hash}`],
|
|
135
|
+
data: { guard: data.guardName || 'unknown' }
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check for errors indicating limits
|
|
140
|
+
if (obs.error || data.error) {
|
|
141
|
+
constraints.push({
|
|
142
|
+
type: 'error-boundary',
|
|
143
|
+
description: obs.error || data.error || 'Error encountered',
|
|
144
|
+
derivedFrom: [`urn:kgc:obs:${hash}`],
|
|
145
|
+
data: { errorType: data.errorType || 'unknown' }
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check for explicit limits
|
|
150
|
+
if (data.maxMemory !== undefined) {
|
|
151
|
+
constraints.push({
|
|
152
|
+
type: 'memory-limit',
|
|
153
|
+
description: `Maximum memory: ${data.maxMemory}`,
|
|
154
|
+
derivedFrom: [`urn:kgc:obs:${hash}`],
|
|
155
|
+
data: { limit: data.maxMemory }
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (data.maxStackDepth !== undefined) {
|
|
160
|
+
constraints.push({
|
|
161
|
+
type: 'stack-depth-limit',
|
|
162
|
+
description: `Maximum stack depth: ${data.maxStackDepth}`,
|
|
163
|
+
derivedFrom: [`urn:kgc:obs:${hash}`],
|
|
164
|
+
data: { limit: data.maxStackDepth }
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return constraints;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Convert observations to RDF/Turtle format
|
|
174
|
+
*
|
|
175
|
+
* Generates Turtle with:
|
|
176
|
+
* - kgc:Observation for each observation
|
|
177
|
+
* - kgc:Capability for derived capabilities
|
|
178
|
+
* - kgc:Constraint for derived constraints
|
|
179
|
+
* - Provenance links via kgc:derivedFrom
|
|
180
|
+
*
|
|
181
|
+
* @param {Array<Object>} observations - Array of observation objects
|
|
182
|
+
* @returns {Promise<string>} Turtle-formatted RDF string
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* const obs = [{ method: 'runtime.node-version', outputs: { version: 'v22.21.1' } }];
|
|
186
|
+
* const turtle = await convertToTurtle(obs);
|
|
187
|
+
* console.log(turtle); // @prefix kgc: <https://unrdf.org/kgc/probe#> . ...
|
|
188
|
+
*/
|
|
189
|
+
export async function convertToTurtle(observations) {
|
|
190
|
+
// Create RDF store
|
|
191
|
+
const store = createStore();
|
|
192
|
+
|
|
193
|
+
// Sort observations by hash for deterministic output
|
|
194
|
+
const sortedObs = [...observations].sort((a, b) => {
|
|
195
|
+
const hashA = generateHash(a);
|
|
196
|
+
const hashB = generateHash(b);
|
|
197
|
+
return hashA.localeCompare(hashB);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Add observations as RDF quads
|
|
201
|
+
for (const obs of sortedObs) {
|
|
202
|
+
const hash = generateHash(obs);
|
|
203
|
+
const obsUri = dataFactory.namedNode(`urn:kgc:obs:${hash}`);
|
|
204
|
+
|
|
205
|
+
// Type declaration
|
|
206
|
+
store.add(dataFactory.quad(
|
|
207
|
+
obsUri,
|
|
208
|
+
dataFactory.namedNode(`${RDF_NS}type`),
|
|
209
|
+
dataFactory.namedNode(kgcUri('Observation'))
|
|
210
|
+
));
|
|
211
|
+
|
|
212
|
+
// Domain (category or inferred from method)
|
|
213
|
+
const domain = obs.domain || obs.category || 'unknown';
|
|
214
|
+
store.add(dataFactory.quad(
|
|
215
|
+
obsUri,
|
|
216
|
+
dataFactory.namedNode(kgcUri('domain')),
|
|
217
|
+
dataFactory.literal(domain)
|
|
218
|
+
));
|
|
219
|
+
|
|
220
|
+
// Method (from method field or message)
|
|
221
|
+
const method = obs.method || obs.message || 'unknown';
|
|
222
|
+
store.add(dataFactory.quad(
|
|
223
|
+
obsUri,
|
|
224
|
+
dataFactory.namedNode(kgcUri('method')),
|
|
225
|
+
dataFactory.literal(method)
|
|
226
|
+
));
|
|
227
|
+
|
|
228
|
+
// Timestamp
|
|
229
|
+
const timestamp = obs.timestamp || obs.metadata?.timestamp || Date.now();
|
|
230
|
+
const timestampDate = typeof timestamp === 'number'
|
|
231
|
+
? new Date(timestamp).toISOString()
|
|
232
|
+
: timestamp;
|
|
233
|
+
|
|
234
|
+
store.add(dataFactory.quad(
|
|
235
|
+
obsUri,
|
|
236
|
+
dataFactory.namedNode(kgcUri('timestamp')),
|
|
237
|
+
dataFactory.literal(timestampDate, dataFactory.namedNode(`${XSD_NS}dateTime`))
|
|
238
|
+
));
|
|
239
|
+
|
|
240
|
+
// Hash
|
|
241
|
+
store.add(dataFactory.quad(
|
|
242
|
+
obsUri,
|
|
243
|
+
dataFactory.namedNode(kgcUri('hash')),
|
|
244
|
+
dataFactory.literal(hash)
|
|
245
|
+
));
|
|
246
|
+
|
|
247
|
+
// Outputs (serialize data/outputs as JSON)
|
|
248
|
+
const outputs = obs.outputs || obs.data || {};
|
|
249
|
+
store.add(dataFactory.quad(
|
|
250
|
+
obsUri,
|
|
251
|
+
dataFactory.namedNode(kgcUri('outputs')),
|
|
252
|
+
dataFactory.literal(JSON.stringify(outputs), dataFactory.namedNode(`${RDF_NS}JSON`))
|
|
253
|
+
));
|
|
254
|
+
|
|
255
|
+
// Optional: Guard decision
|
|
256
|
+
if (obs.guardDecision) {
|
|
257
|
+
store.add(dataFactory.quad(
|
|
258
|
+
obsUri,
|
|
259
|
+
dataFactory.namedNode(kgcUri('guardDecision')),
|
|
260
|
+
dataFactory.literal(obs.guardDecision)
|
|
261
|
+
));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Optional: Error
|
|
265
|
+
if (obs.error) {
|
|
266
|
+
store.add(dataFactory.quad(
|
|
267
|
+
obsUri,
|
|
268
|
+
dataFactory.namedNode(kgcUri('error')),
|
|
269
|
+
dataFactory.literal(obs.error)
|
|
270
|
+
));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Optional: Severity (if from orchestrator schema)
|
|
274
|
+
if (obs.severity) {
|
|
275
|
+
store.add(dataFactory.quad(
|
|
276
|
+
obsUri,
|
|
277
|
+
dataFactory.namedNode(kgcUri('severity')),
|
|
278
|
+
dataFactory.literal(obs.severity)
|
|
279
|
+
));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Derive and add capabilities
|
|
284
|
+
const capabilities = deriveCapabilities(sortedObs);
|
|
285
|
+
for (let i = 0; i < capabilities.length; i++) {
|
|
286
|
+
const cap = capabilities[i];
|
|
287
|
+
const capUri = dataFactory.namedNode(`urn:kgc:cap:${i + 1}`);
|
|
288
|
+
|
|
289
|
+
store.add(dataFactory.quad(
|
|
290
|
+
capUri,
|
|
291
|
+
dataFactory.namedNode(`${RDF_NS}type`),
|
|
292
|
+
dataFactory.namedNode(kgcUri('Capability'))
|
|
293
|
+
));
|
|
294
|
+
|
|
295
|
+
store.add(dataFactory.quad(
|
|
296
|
+
capUri,
|
|
297
|
+
dataFactory.namedNode(kgcUri('name')),
|
|
298
|
+
dataFactory.literal(cap.name)
|
|
299
|
+
));
|
|
300
|
+
|
|
301
|
+
store.add(dataFactory.quad(
|
|
302
|
+
capUri,
|
|
303
|
+
dataFactory.namedNode(kgcUri('available')),
|
|
304
|
+
dataFactory.literal(String(cap.available), dataFactory.namedNode(`${XSD_NS}boolean`))
|
|
305
|
+
));
|
|
306
|
+
|
|
307
|
+
// Link to observations
|
|
308
|
+
for (const obsRef of cap.derivedFrom) {
|
|
309
|
+
store.add(dataFactory.quad(
|
|
310
|
+
capUri,
|
|
311
|
+
dataFactory.namedNode(kgcUri('derivedFrom')),
|
|
312
|
+
dataFactory.namedNode(obsRef)
|
|
313
|
+
));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Derive and add constraints
|
|
318
|
+
const constraints = deriveConstraints(sortedObs);
|
|
319
|
+
for (let i = 0; i < constraints.length; i++) {
|
|
320
|
+
const constraint = constraints[i];
|
|
321
|
+
const constraintUri = dataFactory.namedNode(`urn:kgc:constraint:${i + 1}`);
|
|
322
|
+
|
|
323
|
+
store.add(dataFactory.quad(
|
|
324
|
+
constraintUri,
|
|
325
|
+
dataFactory.namedNode(`${RDF_NS}type`),
|
|
326
|
+
dataFactory.namedNode(kgcUri('Constraint'))
|
|
327
|
+
));
|
|
328
|
+
|
|
329
|
+
store.add(dataFactory.quad(
|
|
330
|
+
constraintUri,
|
|
331
|
+
dataFactory.namedNode(kgcUri('constraintType')),
|
|
332
|
+
dataFactory.literal(constraint.type)
|
|
333
|
+
));
|
|
334
|
+
|
|
335
|
+
store.add(dataFactory.quad(
|
|
336
|
+
constraintUri,
|
|
337
|
+
dataFactory.namedNode(kgcUri('description')),
|
|
338
|
+
dataFactory.literal(constraint.description)
|
|
339
|
+
));
|
|
340
|
+
|
|
341
|
+
// Link to observations
|
|
342
|
+
for (const obsRef of constraint.derivedFrom) {
|
|
343
|
+
store.add(dataFactory.quad(
|
|
344
|
+
constraintUri,
|
|
345
|
+
dataFactory.namedNode(kgcUri('derivedFrom')),
|
|
346
|
+
dataFactory.namedNode(obsRef)
|
|
347
|
+
));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Serialize to Turtle using N3.js Writer
|
|
352
|
+
// Note: Oxigraph dump() may not support 'turtle' format in all versions
|
|
353
|
+
// So we extract quads and use N3.js Writer for compatibility
|
|
354
|
+
const N3 = await import('n3');
|
|
355
|
+
const writer = new N3.Writer({ format: 'Turtle' });
|
|
356
|
+
|
|
357
|
+
// Get all quads from store
|
|
358
|
+
const quads = store.match();
|
|
359
|
+
|
|
360
|
+
// Add quads to writer
|
|
361
|
+
for (const quad of quads) {
|
|
362
|
+
writer.addQuad(quad);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Return serialized Turtle
|
|
366
|
+
return new Promise((resolve, reject) => {
|
|
367
|
+
writer.end((error, result) => {
|
|
368
|
+
if (error) reject(error);
|
|
369
|
+
else resolve(result);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export {
|
|
375
|
+
deriveCapabilities,
|
|
376
|
+
deriveConstraints
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
export default {
|
|
380
|
+
convertToTurtle,
|
|
381
|
+
deriveCapabilities,
|
|
382
|
+
deriveConstraints
|
|
383
|
+
};
|