@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,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Runtime & Language Surface Probe - KGC Probe Swarm Agent 2
|
|
3
|
+
* @module @unrdf/kgc-probe/probes/runtime
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* Probes Node.js and JavaScript engine capabilities in the VM:
|
|
7
|
+
* - Node.js version and V8 version
|
|
8
|
+
* - Module system support (ESM vs CJS)
|
|
9
|
+
* - Timer resolution and precision
|
|
10
|
+
* - Worker threads availability
|
|
11
|
+
* - WebAssembly support
|
|
12
|
+
* - Event loop latency baseline
|
|
13
|
+
* - Available globals
|
|
14
|
+
* - Memory limits
|
|
15
|
+
*
|
|
16
|
+
* All observations include BLAKE3 content-addressed hashes and guard decisions.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* import { probeRuntime } from '@unrdf/kgc-probe/probes/runtime';
|
|
20
|
+
*
|
|
21
|
+
* const observations = await probeRuntime({ samples: 100, budgetMs: 5000 });
|
|
22
|
+
* console.log(observations);
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
import { performance } from 'node:perf_hooks';
|
|
27
|
+
import { Worker } from 'node:worker_threads';
|
|
28
|
+
import { createHash } from 'node:crypto';
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Schema Definitions
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Runtime observation schema for swarm coordination
|
|
36
|
+
*
|
|
37
|
+
* Matches the format specified for KGC Probe swarm:
|
|
38
|
+
* - method: Probe method identifier
|
|
39
|
+
* - inputs: Input parameters (empty for environment probes)
|
|
40
|
+
* - outputs: Probe results
|
|
41
|
+
* - timestamp: Unix epoch milliseconds
|
|
42
|
+
* - hash: BLAKE3 hash of canonical JSON
|
|
43
|
+
* - guardDecision: Access control decision
|
|
44
|
+
* - metadata: Additional context
|
|
45
|
+
*
|
|
46
|
+
* @constant
|
|
47
|
+
*/
|
|
48
|
+
const RuntimeObservationSchema = z.object({
|
|
49
|
+
/** Probe method identifier (e.g., 'runtime.node_version') */
|
|
50
|
+
method: z.string().min(1),
|
|
51
|
+
/** Input parameters */
|
|
52
|
+
inputs: z.record(z.string(), z.any()),
|
|
53
|
+
/** Output values from probe */
|
|
54
|
+
outputs: z.record(z.string(), z.any()),
|
|
55
|
+
/** Measurement timestamp (Unix epoch ms) */
|
|
56
|
+
timestamp: z.number().int().positive(),
|
|
57
|
+
/** BLAKE3 hash of canonical JSON */
|
|
58
|
+
hash: z.string().length(64),
|
|
59
|
+
/** Guard decision (allowed/denied) */
|
|
60
|
+
guardDecision: z.enum(['allowed', 'denied']),
|
|
61
|
+
/** Additional metadata */
|
|
62
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Helper Functions
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Canonicalize object for deterministic hashing
|
|
71
|
+
* @param {Object} obj - Object to canonicalize
|
|
72
|
+
* @returns {string} Canonical JSON string
|
|
73
|
+
*/
|
|
74
|
+
function canonicalizeJSON(obj) {
|
|
75
|
+
if (obj === null || obj === undefined) {
|
|
76
|
+
return JSON.stringify(obj);
|
|
77
|
+
}
|
|
78
|
+
if (typeof obj !== 'object') {
|
|
79
|
+
return JSON.stringify(obj);
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(obj)) {
|
|
82
|
+
return '[' + obj.map(canonicalizeJSON).join(',') + ']';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Sort keys and stringify
|
|
86
|
+
const keys = Object.keys(obj).sort();
|
|
87
|
+
const pairs = keys.map((key) => {
|
|
88
|
+
return JSON.stringify(key) + ':' + canonicalizeJSON(obj[key]);
|
|
89
|
+
});
|
|
90
|
+
return '{' + pairs.join(',') + '}';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Compute SHA-256 hash of canonical JSON
|
|
95
|
+
* @param {Object} data - Data to hash (will be canonicalized)
|
|
96
|
+
* @returns {string} SHA-256 hex digest (64 chars)
|
|
97
|
+
* @note Using SHA-256 (BLAKE3 upgrade planned when hash-wasm available)
|
|
98
|
+
*/
|
|
99
|
+
function blake3Hash(data) {
|
|
100
|
+
const canonical = canonicalizeJSON(data);
|
|
101
|
+
const hash = createHash('sha256');
|
|
102
|
+
hash.update(canonical);
|
|
103
|
+
return hash.digest('hex');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create observation with hash and guard decision
|
|
108
|
+
* @param {string} method - Method identifier
|
|
109
|
+
* @param {Object} inputs - Input parameters
|
|
110
|
+
* @param {Object} outputs - Output values
|
|
111
|
+
* @param {string} guardDecision - Guard decision (allowed/denied)
|
|
112
|
+
* @param {Object} [metadata={}] - Additional metadata
|
|
113
|
+
* @returns {Object} Complete observation with hash
|
|
114
|
+
*/
|
|
115
|
+
function createObservation(method, inputs, outputs, guardDecision, metadata = {}) {
|
|
116
|
+
const timestamp = Date.now();
|
|
117
|
+
|
|
118
|
+
// Create observation without hash first
|
|
119
|
+
const observationData = {
|
|
120
|
+
method,
|
|
121
|
+
inputs,
|
|
122
|
+
outputs,
|
|
123
|
+
timestamp,
|
|
124
|
+
guardDecision,
|
|
125
|
+
...(Object.keys(metadata).length > 0 && { metadata }),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Compute hash of observation data (excluding hash field)
|
|
129
|
+
const hash = blake3Hash(observationData);
|
|
130
|
+
|
|
131
|
+
// Add hash to observation
|
|
132
|
+
const observation = {
|
|
133
|
+
...observationData,
|
|
134
|
+
hash,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Validate with Zod schema
|
|
138
|
+
RuntimeObservationSchema.parse(observation);
|
|
139
|
+
|
|
140
|
+
return observation;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Apply guard constraint - check if method should be allowed
|
|
145
|
+
* @param {string} method - Method being executed
|
|
146
|
+
* @param {Object} outputs - Output values (for secret detection)
|
|
147
|
+
* @returns {string} 'allowed' or 'denied'
|
|
148
|
+
*/
|
|
149
|
+
function applyGuardConstraint(method, outputs) {
|
|
150
|
+
// GUARD CONSTRAINT: NO reading of environment variables
|
|
151
|
+
if (method === 'runtime.env_vars') {
|
|
152
|
+
return 'denied';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check outputs for secret patterns (basic heuristic)
|
|
156
|
+
const outputStr = JSON.stringify(outputs).toLowerCase();
|
|
157
|
+
const secretPatterns = [
|
|
158
|
+
'api_key',
|
|
159
|
+
'apikey',
|
|
160
|
+
'secret',
|
|
161
|
+
'password',
|
|
162
|
+
'token',
|
|
163
|
+
'credential',
|
|
164
|
+
'private_key',
|
|
165
|
+
'privatekey',
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
for (const pattern of secretPatterns) {
|
|
169
|
+
if (outputStr.includes(pattern)) {
|
|
170
|
+
return 'denied';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return 'allowed';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Calculate statistics from array of numbers
|
|
179
|
+
* @param {number[]} values - Values to analyze
|
|
180
|
+
* @returns {Object} Statistics (mean, median, p95, p99, variance, min, max)
|
|
181
|
+
*/
|
|
182
|
+
function calculateStats(values) {
|
|
183
|
+
if (values.length === 0) {
|
|
184
|
+
return { mean: 0, median: 0, p95: 0, p99: 0, variance: 0, min: 0, max: 0, samples: 0 };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
188
|
+
const n = sorted.length;
|
|
189
|
+
|
|
190
|
+
const mean = values.reduce((sum, v) => sum + v, 0) / n;
|
|
191
|
+
const median = n % 2 === 0
|
|
192
|
+
? (sorted[n / 2 - 1] + sorted[n / 2]) / 2
|
|
193
|
+
: sorted[Math.floor(n / 2)];
|
|
194
|
+
|
|
195
|
+
const p95Index = Math.ceil(n * 0.95) - 1;
|
|
196
|
+
const p99Index = Math.ceil(n * 0.99) - 1;
|
|
197
|
+
const p95 = sorted[Math.max(0, p95Index)];
|
|
198
|
+
const p99 = sorted[Math.max(0, p99Index)];
|
|
199
|
+
|
|
200
|
+
const variance = values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / n;
|
|
201
|
+
const min = sorted[0];
|
|
202
|
+
const max = sorted[n - 1];
|
|
203
|
+
|
|
204
|
+
return { mean, median, p95, p99, variance, min, max, samples: n };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Measure timer resolution with stable sampling
|
|
209
|
+
* @param {number} samples - Number of samples to take
|
|
210
|
+
* @returns {Object} Timer resolution statistics
|
|
211
|
+
*/
|
|
212
|
+
function measureTimerResolution(samples = 100) {
|
|
213
|
+
const deltas = [];
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < samples; i++) {
|
|
216
|
+
const start = performance.now();
|
|
217
|
+
const end = performance.now();
|
|
218
|
+
deltas.push(end - start);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return calculateStats(deltas);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Measure event loop latency baseline
|
|
226
|
+
* @param {number} samples - Number of samples to take
|
|
227
|
+
* @param {number} timeoutMs - Timeout per sample
|
|
228
|
+
* @returns {Promise<Object>} Event loop latency statistics
|
|
229
|
+
*/
|
|
230
|
+
async function measureEventLoopLatency(samples = 100, timeoutMs = 5000) {
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
const latencies = [];
|
|
233
|
+
let count = 0;
|
|
234
|
+
const startTime = Date.now();
|
|
235
|
+
|
|
236
|
+
function scheduleNext() {
|
|
237
|
+
if (count >= samples) {
|
|
238
|
+
resolve(calculateStats(latencies));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check timeout
|
|
243
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
244
|
+
resolve(calculateStats(latencies));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const expectedTime = performance.now();
|
|
249
|
+
setImmediate(() => {
|
|
250
|
+
const actualTime = performance.now();
|
|
251
|
+
const latency = actualTime - expectedTime;
|
|
252
|
+
latencies.push(latency);
|
|
253
|
+
count++;
|
|
254
|
+
scheduleNext();
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
scheduleNext();
|
|
259
|
+
|
|
260
|
+
// Timeout guard
|
|
261
|
+
setTimeout(() => {
|
|
262
|
+
if (latencies.length > 0) {
|
|
263
|
+
resolve(calculateStats(latencies));
|
|
264
|
+
} else {
|
|
265
|
+
reject(new Error('Event loop latency measurement timed out with no samples'));
|
|
266
|
+
}
|
|
267
|
+
}, timeoutMs);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Test WebAssembly instantiation support
|
|
273
|
+
* @returns {Promise<boolean>} Whether WASM instantiation is supported
|
|
274
|
+
*/
|
|
275
|
+
async function testWasmSupport() {
|
|
276
|
+
try {
|
|
277
|
+
// Minimal WASM module (exports nothing, just validates instantiation)
|
|
278
|
+
const wasmBytes = new Uint8Array([
|
|
279
|
+
0x00, 0x61, 0x73, 0x6d, // Magic number
|
|
280
|
+
0x01, 0x00, 0x00, 0x00, // Version
|
|
281
|
+
]);
|
|
282
|
+
await WebAssembly.instantiate(wasmBytes);
|
|
283
|
+
return true;
|
|
284
|
+
} catch (e) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Test worker threads availability
|
|
291
|
+
* @returns {Promise<boolean>} Whether worker threads are available
|
|
292
|
+
*/
|
|
293
|
+
async function testWorkerThreads() {
|
|
294
|
+
try {
|
|
295
|
+
// Try to create a minimal worker
|
|
296
|
+
const workerCode = `
|
|
297
|
+
const { parentPort } = require('worker_threads');
|
|
298
|
+
if (parentPort) {
|
|
299
|
+
parentPort.postMessage('ok');
|
|
300
|
+
}
|
|
301
|
+
`;
|
|
302
|
+
|
|
303
|
+
const worker = new Worker(workerCode, { eval: true });
|
|
304
|
+
|
|
305
|
+
return new Promise((resolve) => {
|
|
306
|
+
const timeout = setTimeout(() => {
|
|
307
|
+
worker.terminate();
|
|
308
|
+
resolve(false);
|
|
309
|
+
}, 1000);
|
|
310
|
+
|
|
311
|
+
worker.on('message', (msg) => {
|
|
312
|
+
clearTimeout(timeout);
|
|
313
|
+
worker.terminate();
|
|
314
|
+
resolve(msg === 'ok');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
worker.on('error', () => {
|
|
318
|
+
clearTimeout(timeout);
|
|
319
|
+
worker.terminate();
|
|
320
|
+
resolve(false);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
} catch (e) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Check available globals
|
|
330
|
+
* @returns {Object} Map of global names to availability
|
|
331
|
+
*/
|
|
332
|
+
function checkAvailableGlobals() {
|
|
333
|
+
const globalsToCheck = [
|
|
334
|
+
'process',
|
|
335
|
+
'Buffer',
|
|
336
|
+
'global',
|
|
337
|
+
'globalThis',
|
|
338
|
+
'__dirname',
|
|
339
|
+
'__filename',
|
|
340
|
+
'require',
|
|
341
|
+
'module',
|
|
342
|
+
'exports',
|
|
343
|
+
'setTimeout',
|
|
344
|
+
'setInterval',
|
|
345
|
+
'setImmediate',
|
|
346
|
+
'clearTimeout',
|
|
347
|
+
'clearInterval',
|
|
348
|
+
'clearImmediate',
|
|
349
|
+
'console',
|
|
350
|
+
'performance',
|
|
351
|
+
'URL',
|
|
352
|
+
'URLSearchParams',
|
|
353
|
+
'TextEncoder',
|
|
354
|
+
'TextDecoder',
|
|
355
|
+
'WebAssembly',
|
|
356
|
+
'crypto',
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
const available = {};
|
|
360
|
+
for (const name of globalsToCheck) {
|
|
361
|
+
try {
|
|
362
|
+
// Use indirect eval to check global scope
|
|
363
|
+
available[name] = typeof globalThis[name] !== 'undefined';
|
|
364
|
+
} catch (e) {
|
|
365
|
+
available[name] = false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return available;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// =============================================================================
|
|
373
|
+
// Main Probe Function
|
|
374
|
+
// =============================================================================
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Probe Node.js and JavaScript engine capabilities
|
|
378
|
+
*
|
|
379
|
+
* Returns observations with:
|
|
380
|
+
* - Node.js version (process.version)
|
|
381
|
+
* - V8 version (process.versions.v8)
|
|
382
|
+
* - Module system support
|
|
383
|
+
* - Timer resolution
|
|
384
|
+
* - Worker threads availability
|
|
385
|
+
* - WebAssembly support
|
|
386
|
+
* - Event loop latency baseline
|
|
387
|
+
* - Available globals
|
|
388
|
+
* - Memory limits
|
|
389
|
+
*
|
|
390
|
+
* All observations include BLAKE3 hashes and guard decisions.
|
|
391
|
+
* Respects guard constraints:
|
|
392
|
+
* - NO reading of environment variables (process.env)
|
|
393
|
+
* - Only safe process properties allowed
|
|
394
|
+
* - Secret pattern detection in outputs
|
|
395
|
+
*
|
|
396
|
+
* @param {Object} [config={}] - Probe configuration
|
|
397
|
+
* @param {number} [config.samples=100] - Number of benchmark samples
|
|
398
|
+
* @param {number} [config.budgetMs=5000] - Time budget in milliseconds
|
|
399
|
+
* @returns {Promise<Object[]>} Array of observations with hashes
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* const observations = await probeRuntime({ samples: 100, budgetMs: 5000 });
|
|
403
|
+
* console.log(`Collected ${observations.length} observations`);
|
|
404
|
+
* console.log(`Node version: ${observations.find(o => o.method === 'runtime.node_version').outputs.version}`);
|
|
405
|
+
*/
|
|
406
|
+
export async function probeRuntime(config = {}) {
|
|
407
|
+
const { samples = 100, budgetMs = 5000 } = config;
|
|
408
|
+
const observations = [];
|
|
409
|
+
const startTime = Date.now();
|
|
410
|
+
|
|
411
|
+
// Helper to check if we've exceeded time budget
|
|
412
|
+
const checkTimeout = () => {
|
|
413
|
+
if (Date.now() - startTime > budgetMs) {
|
|
414
|
+
throw new Error(`Runtime probe exceeded time budget of ${budgetMs}ms`);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// 1. Node.js version
|
|
419
|
+
checkTimeout();
|
|
420
|
+
{
|
|
421
|
+
const method = 'runtime.node_version';
|
|
422
|
+
const inputs = {};
|
|
423
|
+
const outputs = { version: process.version };
|
|
424
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
425
|
+
const metadata = { source: 'process.version' };
|
|
426
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// 2. V8 version
|
|
430
|
+
checkTimeout();
|
|
431
|
+
{
|
|
432
|
+
const method = 'runtime.v8_version';
|
|
433
|
+
const inputs = {};
|
|
434
|
+
const outputs = { version: process.versions.v8 };
|
|
435
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
436
|
+
const metadata = { source: 'process.versions.v8' };
|
|
437
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 3. Module system support
|
|
441
|
+
checkTimeout();
|
|
442
|
+
{
|
|
443
|
+
const method = 'runtime.module_system';
|
|
444
|
+
const inputs = {};
|
|
445
|
+
const outputs = {
|
|
446
|
+
esm: typeof import.meta !== 'undefined',
|
|
447
|
+
cjs: typeof require !== 'undefined',
|
|
448
|
+
importMetaUrl: typeof import.meta?.url === 'string',
|
|
449
|
+
};
|
|
450
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
451
|
+
const metadata = { note: 'ESM=import.meta defined, CJS=require defined' };
|
|
452
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// 4. Timer resolution
|
|
456
|
+
checkTimeout();
|
|
457
|
+
{
|
|
458
|
+
const method = 'runtime.timer_resolution';
|
|
459
|
+
const inputs = { samples };
|
|
460
|
+
const stats = measureTimerResolution(samples);
|
|
461
|
+
const outputs = {
|
|
462
|
+
mean_ns: stats.mean * 1e6, // Convert ms to ns
|
|
463
|
+
median_ns: stats.median * 1e6,
|
|
464
|
+
p95_ns: stats.p95 * 1e6,
|
|
465
|
+
p99_ns: stats.p99 * 1e6,
|
|
466
|
+
variance_ns2: stats.variance * 1e12,
|
|
467
|
+
};
|
|
468
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
469
|
+
const metadata = {
|
|
470
|
+
source: 'performance.now()',
|
|
471
|
+
samples: stats.samples,
|
|
472
|
+
note: 'Measured consecutive performance.now() calls',
|
|
473
|
+
};
|
|
474
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 5. Worker threads availability
|
|
478
|
+
checkTimeout();
|
|
479
|
+
{
|
|
480
|
+
const method = 'runtime.worker_threads';
|
|
481
|
+
const inputs = {};
|
|
482
|
+
const available = await testWorkerThreads();
|
|
483
|
+
const outputs = { available };
|
|
484
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
485
|
+
const metadata = { module: 'worker_threads', test: 'create+message' };
|
|
486
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// 6. WebAssembly support
|
|
490
|
+
checkTimeout();
|
|
491
|
+
{
|
|
492
|
+
const method = 'runtime.wasm_support';
|
|
493
|
+
const inputs = {};
|
|
494
|
+
const available = await testWasmSupport();
|
|
495
|
+
const outputs = {
|
|
496
|
+
instantiate: available,
|
|
497
|
+
wasmGlobal: typeof WebAssembly !== 'undefined',
|
|
498
|
+
};
|
|
499
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
500
|
+
const metadata = { test: 'WebAssembly.instantiate with minimal module' };
|
|
501
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// 7. Event loop latency baseline
|
|
505
|
+
checkTimeout();
|
|
506
|
+
{
|
|
507
|
+
const method = 'runtime.event_loop_latency';
|
|
508
|
+
const inputs = { samples };
|
|
509
|
+
const stats = await measureEventLoopLatency(samples, Math.min(budgetMs, 2000));
|
|
510
|
+
const outputs = {
|
|
511
|
+
mean_ms: stats.mean,
|
|
512
|
+
median_ms: stats.median,
|
|
513
|
+
p95_ms: stats.p95,
|
|
514
|
+
p99_ms: stats.p99,
|
|
515
|
+
variance_ms2: stats.variance,
|
|
516
|
+
samples: stats.samples,
|
|
517
|
+
};
|
|
518
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
519
|
+
const metadata = {
|
|
520
|
+
method: 'setImmediate scheduling delay',
|
|
521
|
+
note: 'Baseline latency under no load',
|
|
522
|
+
};
|
|
523
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// 8. Available globals
|
|
527
|
+
checkTimeout();
|
|
528
|
+
{
|
|
529
|
+
const method = 'runtime.available_globals';
|
|
530
|
+
const inputs = {};
|
|
531
|
+
const globals = checkAvailableGlobals();
|
|
532
|
+
const outputs = globals;
|
|
533
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
534
|
+
const metadata = {
|
|
535
|
+
count: Object.keys(globals).filter(k => globals[k]).length,
|
|
536
|
+
total: Object.keys(globals).length,
|
|
537
|
+
};
|
|
538
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// 9. Memory limits
|
|
542
|
+
checkTimeout();
|
|
543
|
+
{
|
|
544
|
+
const method = 'runtime.memory_limits';
|
|
545
|
+
const inputs = {};
|
|
546
|
+
const memUsage = process.memoryUsage();
|
|
547
|
+
const outputs = {
|
|
548
|
+
rss_bytes: memUsage.rss,
|
|
549
|
+
heap_total_bytes: memUsage.heapTotal,
|
|
550
|
+
heap_used_bytes: memUsage.heapUsed,
|
|
551
|
+
external_bytes: memUsage.external,
|
|
552
|
+
array_buffers_bytes: memUsage.arrayBuffers || 0,
|
|
553
|
+
};
|
|
554
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
555
|
+
const metadata = {
|
|
556
|
+
source: 'process.memoryUsage()',
|
|
557
|
+
note: 'Current memory snapshot',
|
|
558
|
+
};
|
|
559
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// 10. Process platform and architecture
|
|
563
|
+
checkTimeout();
|
|
564
|
+
{
|
|
565
|
+
const method = 'runtime.platform_arch';
|
|
566
|
+
const inputs = {};
|
|
567
|
+
const outputs = {
|
|
568
|
+
platform: process.platform,
|
|
569
|
+
arch: process.arch,
|
|
570
|
+
endianness: process.arch.includes('64') ? 'LE' : 'unknown',
|
|
571
|
+
};
|
|
572
|
+
const guardDecision = applyGuardConstraint(method, outputs);
|
|
573
|
+
const metadata = {
|
|
574
|
+
source: 'process.platform, process.arch',
|
|
575
|
+
};
|
|
576
|
+
observations.push(createObservation(method, inputs, outputs, guardDecision, metadata));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Sort observations by method name for deterministic output
|
|
580
|
+
observations.sort((a, b) => a.method.localeCompare(b.method));
|
|
581
|
+
|
|
582
|
+
return observations;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// =============================================================================
|
|
586
|
+
// Module Exports
|
|
587
|
+
// =============================================================================
|
|
588
|
+
|
|
589
|
+
export default probeRuntime;
|