@svrnsec/pulse 0.5.0 → 0.7.0

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Cross-Metric Heuristic Engine
2
+ * @svrnsec/pulse — Cross-Metric Heuristic Engine
3
3
  *
4
4
  * Instead of checking individual thresholds in isolation, this module looks
5
5
  * at the *relationships* between metrics. A sophisticated adversary can spoof
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Statistical Jitter Analysis
2
+ * @svrnsec/pulse — Statistical Jitter Analysis
3
3
  *
4
4
  * Analyses the timing distribution from the entropy probe to classify
5
5
  * the host as a real consumer device or a sanitised datacenter VM.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — LLM / AI Agent Behavioral Fingerprint
2
+ * @svrnsec/pulse — LLM / AI Agent Behavioral Fingerprint
3
3
  *
4
4
  * Detects automation driven by large language models, headless browsers
5
5
  * controlled by AI agents (AutoGPT, CrewAI, browser-use, Playwright+LLM,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Hypervisor & Cloud Provider Fingerprinter
2
+ * @svrnsec/pulse — Hypervisor & Cloud Provider Fingerprinter
3
3
  *
4
4
  * Each hypervisor has a distinct "steal-time rhythm" — a characteristic
5
5
  * pattern in how it schedules guest vCPUs on host physical cores.
@@ -0,0 +1,391 @@
1
+ /**
2
+ * @svrnsec/pulse — Refraction
3
+ *
4
+ * The same physics signal "refracts" through different measurement mediums.
5
+ * A browser's clamped performance.now() (~100μs Spectre mitigation) shifts
6
+ * every statistical distribution compared to Node.js process.hrtime.bigint().
7
+ *
8
+ * Instead of maintaining two sets of hardcoded thresholds, Refraction:
9
+ * 1. Probes the timer to measure actual resolution
10
+ * 2. Detects the execution environment (browser, Node, Deno, worker, etc.)
11
+ * 3. Computes a calibration profile that shifts all scoring bands
12
+ * 4. Exposes the profile so every downstream analyzer (jitter, trustScore,
13
+ * population entropy) can score against the correct baseline
14
+ *
15
+ * Core insight:
16
+ * A VM in Node.js and real hardware in a browser can produce identical
17
+ * Hurst exponents. Without knowing the medium, the score is meaningless.
18
+ * Refraction makes the medium explicit.
19
+ *
20
+ * Usage:
21
+ * import { calibrate, getProfile } from '@svrnsec/pulse/refraction'
22
+ * const profile = await calibrate() // run once at init
23
+ * const score = classifyJitter(timings, { refraction: profile })
24
+ */
25
+
26
+ // ─── Environment detection ───────────────────────────────────────────────────
27
+
28
+ const ENV = Object.freeze({
29
+ NODE: 'node',
30
+ BROWSER: 'browser',
31
+ WORKER: 'worker', // Web Worker / Service Worker
32
+ DENO: 'deno',
33
+ BUN: 'bun',
34
+ UNKNOWN: 'unknown',
35
+ });
36
+
37
+ /**
38
+ * Detect the current JS runtime without relying on user-agent sniffing.
39
+ * Uses capability detection — what APIs exist, not what strings say.
40
+ */
41
+ function detectEnvironment() {
42
+ // Deno has Deno global
43
+ if (typeof globalThis.Deno !== 'undefined') return ENV.DENO;
44
+ // Bun has Bun global
45
+ if (typeof globalThis.Bun !== 'undefined') return ENV.BUN;
46
+ // Node.js has process.versions.node
47
+ if (typeof globalThis.process !== 'undefined' &&
48
+ globalThis.process.versions?.node) return ENV.NODE;
49
+ // Web Worker has self but no document
50
+ if (typeof globalThis.WorkerGlobalScope !== 'undefined') return ENV.WORKER;
51
+ // Browser has window + document
52
+ if (typeof globalThis.window !== 'undefined' &&
53
+ typeof globalThis.document !== 'undefined') return ENV.BROWSER;
54
+ return ENV.UNKNOWN;
55
+ }
56
+
57
+ // ─── Timer resolution probe ──────────────────────────────────────────────────
58
+
59
+ /**
60
+ * Measure the actual timer resolution by collecting minimum non-zero deltas.
61
+ * Returns resolution in microseconds.
62
+ *
63
+ * Browser (Spectre-mitigated): typically 100μs
64
+ * Node.js hrtime: typically < 1μs
65
+ * Node.js performance.now(): typically ~1μs
66
+ *
67
+ * We probe with performance.now() since it's cross-platform.
68
+ * In Node.js we also check process.hrtime.bigint() availability.
69
+ */
70
+ function probeTimerResolution(iterations = 500) {
71
+ const deltas = [];
72
+ const pnow = typeof performance !== 'undefined' && performance.now
73
+ ? () => performance.now()
74
+ : () => Date.now(); // fallback — 1ms resolution
75
+
76
+ for (let i = 0; i < iterations; i++) {
77
+ const t0 = pnow();
78
+ // Minimal work — just enough to not get optimized away
79
+ let x = 0;
80
+ for (let j = 0; j < 10; j++) x += j;
81
+ const t1 = pnow();
82
+ const dt = t1 - t0;
83
+ if (dt > 0) deltas.push(dt);
84
+ if (x === -1) deltas.push(0); // prevent dead code elimination
85
+ }
86
+
87
+ if (deltas.length === 0) return { resolutionUs: 1000, grain: 'coarse' };
88
+
89
+ deltas.sort((a, b) => a - b);
90
+
91
+ // Minimum non-zero delta = timer resolution floor
92
+ const minDelta = deltas[0];
93
+ // Median gives stable estimate
94
+ const medDelta = deltas[Math.floor(deltas.length / 2)];
95
+ // Count unique values — clamped timers produce few unique values
96
+ const unique = new Set(deltas.map(d => d.toFixed(4))).size;
97
+ const uniqueRatio = unique / deltas.length;
98
+
99
+ const resolutionUs = minDelta * 1000; // ms → μs
100
+
101
+ let grain;
102
+ if (resolutionUs < 5) grain = 'nanosecond'; // < 5μs — Node.js / Deno
103
+ else if (resolutionUs < 50) grain = 'fine'; // 5–50μs — some browsers with relaxed policy
104
+ else if (resolutionUs < 200) grain = 'clamped'; // 50–200μs — standard Spectre mitigation
105
+ else grain = 'coarse'; // > 200μs — aggressive clamping or Date.now()
106
+
107
+ return {
108
+ resolutionUs: +resolutionUs.toFixed(2),
109
+ minDeltaMs: +minDelta.toFixed(6),
110
+ medDeltaMs: +medDelta.toFixed(6),
111
+ uniqueRatio: +uniqueRatio.toFixed(4),
112
+ grain,
113
+ samples: deltas.length,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Check if high-resolution timer is available (Node.js process.hrtime).
119
+ */
120
+ function hasHrtime() {
121
+ return typeof globalThis.process !== 'undefined' &&
122
+ typeof globalThis.process.hrtime?.bigint === 'function';
123
+ }
124
+
125
+ // ─── Threshold profile ───────────────────────────────────────────────────────
126
+
127
+ /**
128
+ * Scoring thresholds shift based on timer grain.
129
+ *
130
+ * Why each threshold changes:
131
+ *
132
+ * CV (Coefficient of Variation):
133
+ * Clamped timers absorb small jitter → CV appears lower on real hardware.
134
+ * But heavy ops (32K+) push deltas above clamp floor, creating burst variance.
135
+ * Browser real HW: CV 0.01–0.90. Node real HW: CV 0.04–0.35.
136
+ *
137
+ * Hurst Exponent:
138
+ * Timer clamping introduces quantization steps that shift H upward.
139
+ * Browser real HW: H 0.15–0.82. Node real HW: H 0.25–0.55.
140
+ *
141
+ * Autocorrelation:
142
+ * Browser event loop scheduling adds baseline AC ~0.3–0.5 on real hardware.
143
+ * VMs in browser still show higher AC > 0.65 from hypervisor tick.
144
+ * Node real HW: AC < 0.20. Browser real HW: AC < 0.50.
145
+ *
146
+ * Quantization Entropy:
147
+ * Fewer unique timer values → fewer populated bins → lower QE.
148
+ * Node real HW: QE > 3.0. Browser real HW: QE > 0.8.
149
+ *
150
+ * Unique Value Ratio:
151
+ * Clamped timers repeat values more.
152
+ * Node real HW: UVR > 0.60. Browser real HW: UVR > 0.15.
153
+ */
154
+
155
+ const PROFILES = {
156
+ nanosecond: {
157
+ label: 'High-resolution timer (Node.js / Deno)',
158
+ cv: { floor: 0.04, ceil: 0.35, vmFloor: 0.02 },
159
+ hurst: { floor: 0.25, ceil: 0.55, vmCeil: 0.60 },
160
+ ac: { pass: 0.20, warn: 0.35, fail: 0.50 },
161
+ qe: { pass: 3.0, warn: 1.5 },
162
+ uvr: { pass: 0.60, warn: 0.30 },
163
+ dram: { elFloor: 2, mcvFloor: 0.04 },
164
+ },
165
+ fine: {
166
+ label: 'Fine timer (relaxed browser policy)',
167
+ cv: { floor: 0.02, ceil: 0.60, vmFloor: 0.01 },
168
+ hurst: { floor: 0.20, ceil: 0.70, vmCeil: 0.75 },
169
+ ac: { pass: 0.35, warn: 0.50, fail: 0.60 },
170
+ qe: { pass: 1.5, warn: 0.8 },
171
+ uvr: { pass: 0.30, warn: 0.15 },
172
+ dram: { elFloor: 3, mcvFloor: 0.03 },
173
+ },
174
+ clamped: {
175
+ label: 'Spectre-mitigated browser timer (~100μs)',
176
+ cv: { floor: 0.01, ceil: 0.90, vmFloor: 0.005 },
177
+ hurst: { floor: 0.15, ceil: 0.82, vmCeil: 0.88 },
178
+ ac: { pass: 0.50, warn: 0.60, fail: 0.70 },
179
+ qe: { pass: 0.8, warn: 0.5 },
180
+ uvr: { pass: 0.15, warn: 0.08 },
181
+ dram: { elFloor: 5, mcvFloor: 0.02 },
182
+ },
183
+ coarse: {
184
+ label: 'Coarse timer (aggressive clamping / Date.now)',
185
+ cv: { floor: 0.005, ceil: 1.0, vmFloor: 0.002 },
186
+ hurst: { floor: 0.10, ceil: 0.88, vmCeil: 0.92 },
187
+ ac: { pass: 0.55, warn: 0.65, fail: 0.75 },
188
+ qe: { pass: 0.5, warn: 0.3 },
189
+ uvr: { pass: 0.08, warn: 0.04 },
190
+ dram: { elFloor: 5, mcvFloor: 0.02 },
191
+ },
192
+ };
193
+
194
+ // ─── Calibration ─────────────────────────────────────────────────────────────
195
+
196
+ /** @type {RefractionProfile|null} */
197
+ let _cached = null;
198
+
199
+ /**
200
+ * Run the full calibration sequence. Call once at init; results are cached.
201
+ *
202
+ * @param {object} [opts]
203
+ * @param {boolean} [opts.force=false] - bypass cache and re-probe
204
+ * @returns {Promise<RefractionProfile>}
205
+ */
206
+ export async function calibrate(opts = {}) {
207
+ if (_cached && !opts.force) return _cached;
208
+
209
+ const env = detectEnvironment();
210
+ const timer = probeTimerResolution();
211
+ const hrtime = hasHrtime();
212
+
213
+ // If Node.js with hrtime, always use nanosecond profile regardless of
214
+ // performance.now() resolution (which may be coarser on some builds).
215
+ const grain = (env === ENV.NODE && hrtime) ? 'nanosecond' : timer.grain;
216
+ const thresholds = PROFILES[grain];
217
+
218
+ _cached = Object.freeze({
219
+ env,
220
+ timer,
221
+ grain,
222
+ hrtime,
223
+ thresholds,
224
+ label: thresholds.label,
225
+ calibratedAt: Date.now(),
226
+ });
227
+
228
+ return _cached;
229
+ }
230
+
231
+ /**
232
+ * Get the cached profile. Returns null if calibrate() hasn't been called.
233
+ * @returns {RefractionProfile|null}
234
+ */
235
+ export function getProfile() {
236
+ return _cached;
237
+ }
238
+
239
+ /**
240
+ * Synchronous calibration for environments that can't await.
241
+ * Slightly less accurate than async version but sufficient for most cases.
242
+ */
243
+ export function calibrateSync() {
244
+ if (_cached) return _cached;
245
+
246
+ const env = detectEnvironment();
247
+ const timer = probeTimerResolution();
248
+ const hrtime = hasHrtime();
249
+ const grain = (env === ENV.NODE && hrtime) ? 'nanosecond' : timer.grain;
250
+ const thresholds = PROFILES[grain];
251
+
252
+ _cached = Object.freeze({
253
+ env,
254
+ timer,
255
+ grain,
256
+ hrtime,
257
+ thresholds,
258
+ label: thresholds.label,
259
+ calibratedAt: Date.now(),
260
+ });
261
+
262
+ return _cached;
263
+ }
264
+
265
+ /**
266
+ * Reset cached profile. Useful for testing.
267
+ */
268
+ export function resetProfile() {
269
+ _cached = null;
270
+ }
271
+
272
+ // ─── Threshold accessors ─────────────────────────────────────────────────────
273
+
274
+ /**
275
+ * Get the active thresholds for a specific signal.
276
+ * Falls back to 'clamped' profile if not calibrated (safe default).
277
+ *
278
+ * @param {'cv'|'hurst'|'ac'|'qe'|'uvr'|'dram'} signal
279
+ * @returns {object}
280
+ */
281
+ export function getThresholds(signal) {
282
+ const profile = _cached?.thresholds ?? PROFILES.clamped;
283
+ return profile[signal];
284
+ }
285
+
286
+ /**
287
+ * Score a value against refraction-aware thresholds.
288
+ * Returns { score: 0-1, pass: boolean, flag: string|null }
289
+ *
290
+ * @param {'cv'|'hurst'|'ac'|'qe'|'uvr'} signal
291
+ * @param {number} value
292
+ * @returns {{ score: number, pass: boolean, flag: string|null }}
293
+ */
294
+ export function scoreSignal(signal, value) {
295
+ const t = getThresholds(signal);
296
+
297
+ switch (signal) {
298
+ case 'cv': {
299
+ if (value >= t.floor && value <= t.ceil) return { score: 1, pass: true, flag: null };
300
+ if (value < t.vmFloor) return { score: 0.3, pass: false, flag: 'CV_FLAT_HYPERVISOR' };
301
+ if (value < t.floor) return { score: 0.6 + (value - t.vmFloor) / (t.floor - t.vmFloor) * 0.4, pass: false, flag: 'CV_LOW_BORDERLINE' };
302
+ if (value > t.ceil) return { score: 0.6, pass: false, flag: 'CV_HIGH_BURST' };
303
+ return { score: 0.5, pass: false, flag: 'CV_ANOMALOUS' };
304
+ }
305
+ case 'hurst': {
306
+ if (value >= t.floor && value <= t.ceil) return { score: 1, pass: true, flag: null };
307
+ if (value > t.vmCeil) return { score: 0.3, pass: false, flag: 'HURST_PERSISTENT_VM' };
308
+ if (value > t.ceil) return { score: 0.6, pass: false, flag: 'HURST_HIGH_BORDERLINE' };
309
+ if (value < t.floor) return { score: 0.65, pass: false, flag: 'HURST_WEAK' };
310
+ return { score: 0.5, pass: false, flag: 'HURST_ANOMALOUS' };
311
+ }
312
+ case 'ac': {
313
+ if (value < t.pass) return { score: 1, pass: true, flag: null };
314
+ if (value < t.warn) return { score: 0.7, pass: false, flag: 'AC_MODERATE' };
315
+ if (value < t.fail) return { score: 0.5, pass: false, flag: 'AC_HIGH' };
316
+ return { score: 0.2, pass: false, flag: 'AC_VM_PERIODIC' };
317
+ }
318
+ case 'qe': {
319
+ if (value >= t.pass) return { score: 1, pass: true, flag: null };
320
+ if (value >= t.warn) return { score: 0.75, pass: false, flag: 'QE_LOW_BORDERLINE' };
321
+ return { score: 0.35, pass: false, flag: 'QE_QUANTIZED' };
322
+ }
323
+ case 'uvr': {
324
+ if (value >= t.pass) return { score: 1, pass: true, flag: null };
325
+ if (value >= t.warn) return { score: 0.7, pass: false, flag: 'UVR_LOW_DIVERSITY' };
326
+ return { score: 0.3, pass: false, flag: 'UVR_CLAMPED' };
327
+ }
328
+ default:
329
+ return { score: 0.5, pass: false, flag: 'UNKNOWN_SIGNAL' };
330
+ }
331
+ }
332
+
333
+ // ─── Composite scoring ───────────────────────────────────────────────────────
334
+
335
+ /**
336
+ * Score an entire jitter analysis result through the refraction lens.
337
+ * This is the primary API — pass your raw stats and get back a
338
+ * refraction-aware score with full breakdown.
339
+ *
340
+ * @param {object} stats - { cv, hurst, ac1, qe, uvr }
341
+ * @returns {{ score: number, signals: object, flags: string[], profile: string }}
342
+ */
343
+ export function scoreJitter(stats) {
344
+ const profile = _cached ?? calibrateSync();
345
+
346
+ const cv = scoreSignal('cv', stats.cv);
347
+ const hurst = scoreSignal('hurst', stats.hurst ?? stats.H);
348
+ const ac = scoreSignal('ac', stats.ac1 ?? stats.a1);
349
+ const qe = scoreSignal('qe', stats.qe ?? stats.QE);
350
+ const uvr = scoreSignal('uvr', stats.uvr ?? stats.ur);
351
+
352
+ const flags = [cv, hurst, ac, qe, uvr]
353
+ .map(s => s.flag)
354
+ .filter(Boolean);
355
+
356
+ // Weighted fusion — same weights regardless of medium
357
+ const raw = cv.score * 0.20 +
358
+ hurst.score * 0.20 +
359
+ ac.score * 0.20 +
360
+ qe.score * 0.15 +
361
+ uvr.score * 0.25;
362
+
363
+ return {
364
+ score: +Math.min(0.99, Math.max(0.01, raw)).toFixed(4),
365
+ signals: { cv, hurst, ac, qe, uvr },
366
+ flags,
367
+ grain: profile.grain,
368
+ profile: profile.label,
369
+ };
370
+ }
371
+
372
+ // ─── Exports ─────────────────────────────────────────────────────────────────
373
+
374
+ export {
375
+ ENV,
376
+ PROFILES,
377
+ detectEnvironment,
378
+ probeTimerResolution,
379
+ hasHrtime,
380
+ };
381
+
382
+ /**
383
+ * @typedef {object} RefractionProfile
384
+ * @property {string} env - 'node' | 'browser' | 'worker' | 'deno' | 'bun' | 'unknown'
385
+ * @property {object} timer - { resolutionUs, minDeltaMs, medDeltaMs, uniqueRatio, grain, samples }
386
+ * @property {string} grain - 'nanosecond' | 'fine' | 'clamped' | 'coarse'
387
+ * @property {boolean} hrtime - true if process.hrtime.bigint() is available
388
+ * @property {object} thresholds - the active PROFILES[grain] threshold set
389
+ * @property {string} label - human-readable description
390
+ * @property {number} calibratedAt - Date.now() when calibration ran
391
+ */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Adaptive Entropy Probe
2
+ * @svrnsec/pulse — Adaptive Entropy Probe
3
3
  *
4
4
  * Runs the WASM probe in batches and stops early once the signal is decisive.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Bio-Binding Layer
2
+ * @svrnsec/pulse — Bio-Binding Layer
3
3
  *
4
4
  * Captures mouse-movement micro-stutters and keystroke-cadence dynamics
5
5
  * WHILE the hardware entropy probe is running. Computes the
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — GPU Canvas Fingerprint
2
+ * @svrnsec/pulse — GPU Canvas Fingerprint
3
3
  *
4
4
  * Collects device-class signals from WebGL and 2D Canvas rendering.
5
5
  * The exact pixel values of GPU-rendered scenes are vendor/driver-specific
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — DRAM Refresh Cycle Detector
2
+ * @svrnsec/pulse — DRAM Refresh Cycle Detector
3
3
  *
4
4
  * DDR4 DRAM refreshes every 7.8 ms (tREFI per JEDEC JESD79-4). During a
5
5
  * refresh, the memory controller stalls all access requests for ~350 ns.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Electrical Network Frequency (ENF) Detection
2
+ * @svrnsec/pulse — Electrical Network Frequency (ENF) Detection
3
3
  *
4
4
  * ┌─────────────────────────────────────────────────────────────────────────┐
5
5
  * │ WHAT THIS IS │
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Entropy Collector
2
+ * @svrnsec/pulse — Entropy Collector
3
3
  *
4
4
  * Bridges the Rust/WASM matrix-multiply probe into JavaScript.
5
5
  * The WASM module is lazily initialised once and cached for subsequent calls.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — WebGPU Thermal Variance Probe
2
+ * @svrnsec/pulse — WebGPU Thermal Variance Probe
3
3
  *
4
4
  * Runs a compute shader on the GPU and measures dispatch timing variance.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — SharedArrayBuffer Microsecond Timer
2
+ * @svrnsec/pulse — SharedArrayBuffer Microsecond Timer
3
3
  *
4
4
  * Bypasses browser timer clamping (Brave 100µs cap, Firefox 20µs cap, Safari
5
5
  * 1ms cap) using Atomics.wait() which is exempt from clamping because it maps
@@ -38,7 +38,7 @@ export function isSabAvailable() {
38
38
  typeof SharedArrayBuffer !== 'undefined' &&
39
39
  typeof Atomics !== 'undefined' &&
40
40
  typeof Atomics.wait === 'function' &&
41
- crossOriginIsolated === true // window flag set by COOP+COEP headers
41
+ typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated === true // COOP+COEP headers
42
42
  );
43
43
  }
44
44
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — High-Level Fingerprint Class
2
+ * @svrnsec/pulse — High-Level Fingerprint Class
3
3
  *
4
4
  * The developer-facing API. Instead of forcing devs to understand Hurst
5
5
  * Exponents and Quantization Entropy, they get a Fingerprint object with
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Usage:
9
9
  *
10
- * import { Fingerprint } from '@sovereign/pulse';
10
+ * import { Fingerprint } from '@svrnsec/pulse';
11
11
  *
12
12
  * const fp = await Fingerprint.collect({ nonce });
13
13
  *
package/src/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * @sovereign/pulse
2
+ * @svrnsec/pulse
3
3
  *
4
4
  * Physical Turing Test — distinguishes a real consumer device with a human
5
5
  * operator from a sanitised Datacenter VM / AI Instance.
6
6
  *
7
7
  * Usage (client-side):
8
8
  *
9
- * import { pulse } from '@sovereign/pulse';
9
+ * import { pulse } from '@svrnsec/pulse';
10
10
  *
11
11
  * // 1. Get a server-issued nonce (prevents replay attacks)
12
12
  * const { nonce } = await fetch('/api/pulse-challenge').then(r => r.json());
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * Usage (server-side):
24
24
  *
25
- * import { validateProof, generateNonce } from '@sovereign/pulse/validator';
25
+ * import { validateProof, generateNonce } from '@svrnsec/pulse/validator';
26
26
  *
27
27
  * // Challenge endpoint
28
28
  * app.get('/api/pulse-challenge', (req, res) => {
@@ -130,7 +130,7 @@ async function _pulseHosted(opts) {
130
130
  // ---------------------------------------------------------------------------
131
131
 
132
132
  /**
133
- * Run the full @sovereign/pulse probe and return a signed commitment.
133
+ * Run the full @svrnsec/pulse probe and return a signed commitment.
134
134
  *
135
135
  * Two modes:
136
136
  * - pulse({ nonce }) — self-hosted (you manage the nonce server)
@@ -149,7 +149,7 @@ export async function pulse(opts = {}) {
149
149
  const { nonce } = opts;
150
150
  if (!nonce || typeof nonce !== 'string') {
151
151
  throw new Error(
152
- '@sovereign/pulse: opts.nonce is required (self-hosted), or pass opts.apiKey for zero-config hosted mode.'
152
+ '@svrnsec/pulse: opts.nonce is required (self-hosted), or pass opts.apiKey for zero-config hosted mode.'
153
153
  );
154
154
  }
155
155
 
@@ -228,7 +228,7 @@ async function _runProbe(opts) {
228
228
  const [enfResult, gpuResult, dramResult, llmResult] = await Promise.all([
229
229
  collectEnfTimings().catch(() => null),
230
230
  collectGpuEntropy().catch(() => null),
231
- collectDramTimings().catch(() => null),
231
+ Promise.resolve(collectDramTimings()).catch(() => null),
232
232
  Promise.resolve(detectLlmAgent(bioSnapshot)).catch(() => null),
233
233
  ]);
234
234
 
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @sovereign/pulse — React Hook
2
+ * @svrnsec/pulse — React Hook
3
3
  *
4
- * import { usePulse } from '@sovereign/pulse/react';
4
+ * import { usePulse } from '@svrnsec/pulse/react';
5
5
  *
6
6
  * const {
7
7
  * run, reset,
@@ -1,12 +1,12 @@
1
1
  /**
2
- * @sovereign/pulse — Express Middleware
2
+ * @svrnsec/pulse — Express Middleware
3
3
  *
4
4
  * Drop-in middleware for Express / Fastify / Hono.
5
5
  * Handles the full challenge → verify flow in two lines of code.
6
6
  *
7
7
  * Usage:
8
8
  *
9
- * import { createPulseMiddleware } from '@sovereign/pulse/middleware/express';
9
+ * import { createPulseMiddleware } from '@svrnsec/pulse/middleware/express';
10
10
  *
11
11
  * const pulse = createPulseMiddleware({ threshold: 0.6 });
12
12
  *
@@ -1,12 +1,12 @@
1
1
  /**
2
- * @sovereign/pulse — Next.js App Router Middleware
2
+ * @svrnsec/pulse — Next.js App Router Middleware
3
3
  *
4
4
  * Works with Next.js App Router (13+) and Edge Runtime.
5
5
  *
6
6
  * ── Route Handler wrapper ──────────────────────────────────────────────────
7
7
  *
8
8
  * // app/api/checkout/route.js
9
- * import { withPulse } from '@sovereign/pulse/middleware/next';
9
+ * import { withPulse } from '@svrnsec/pulse/middleware/next';
10
10
  *
11
11
  * export const POST = withPulse({ threshold: 0.6 })(
12
12
  * async (req) => {
@@ -18,7 +18,7 @@
18
18
  * ── Challenge endpoint (copy-paste ready) ─────────────────────────────────
19
19
  *
20
20
  * // app/api/pulse/challenge/route.js
21
- * import { pulseChallenge } from '@sovereign/pulse/middleware/next';
21
+ * import { pulseChallenge } from '@svrnsec/pulse/middleware/next';
22
22
  * export const GET = pulseChallenge();
23
23
  *
24
24
  * ── Edge-compatible nonce store ────────────────────────────────────────────
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Hardware Fingerprint & Proof Builder
2
+ * @svrnsec/pulse — Hardware Fingerprint & Proof Builder
3
3
  *
4
4
  * Assembles all collected signals into a canonical ProofPayload, then
5
5
  * produces a BLAKE3 commitment: BLAKE3(canonicalJSON(payload)).
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — Server-Side Validator
2
+ * @svrnsec/pulse — Server-Side Validator
3
3
  *
4
4
  * Verifies a ProofPayload + BLAKE3 commitment received from the client.
5
5
  * This module is for NODE.JS / SERVER use only. It should NOT be bundled
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @sovereign/pulse — SVRN Registry Signature Serializer
2
+ * @svrnsec/pulse — SVRN Registry Signature Serializer
3
3
  *
4
4
  * The registry is a crowdsourced database of device "Silicon Signatures" —
5
5
  * compact, privacy-safe profiles that characterise a device class rather than
@@ -19,7 +19,7 @@
19
19
  * - Any user-identifiable information
20
20
  *
21
21
  * Usage:
22
- * import { serializeSignature, matchRegistry, KNOWN_PROFILES } from '@sovereign/pulse/registry';
22
+ * import { serializeSignature, matchRegistry, KNOWN_PROFILES } from '@svrnsec/pulse/registry';
23
23
  *
24
24
  * const sig = serializeSignature(fingerprint);
25
25
  * const match = matchRegistry(sig, KNOWN_PROFILES);
@@ -53,8 +53,8 @@ export const KNOWN_PROFILES = [
53
53
  },
54
54
  {
55
55
  id: 'kvm-vps-ubuntu22-2vcpu',
56
- name: 'KVM VPS / Ubuntu 22.04 / 2 vCPU (192.222.57.254)',
57
- class: 'cloud-vm-budget',
56
+ name: 'KVM VM / Ubuntu 22.04 / 12 vCPU / 480GB RAM / NVIDIA GH200 Grace Hopper (192.222.57.254)',
57
+ class: 'datacenter-gpu-highend',
58
58
  profile: 'picket-fence',
59
59
  provider: 'kvm-generic',
60
60
  metrics: {