@blamejs/core 0.7.97 → 0.7.98
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/CHANGELOG.md +2 -0
- package/lib/ntp-check.js +76 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.7.x
|
|
10
10
|
|
|
11
|
+
- **0.7.98** (2026-05-06) — `b.ntpCheck.monitor({ intervalMs, ... })` — periodic clock-drift monitor that runs `checkDrift` on a schedule and emits audit + observability events on threshold crossings. Returns a handle with `.stop()` for graceful shutdown. Audit emissions: `system.ntp.checked` (every check), `system.ntp.drift_warn` (drift exceeds warn threshold), `system.ntp.drift_fatal` (drift exceeds fatal threshold), `system.ntp.unreachable` (every server in the list failed to respond). Observability event: `ntp.drift_ms` gauge on every successful check, labeled with the responding server. Optional `onDrift(result)` operator hook fires on every warning/fatal check so operators can wire pager / Slack notifications. The previous boot-time-only `b.ntpCheck.bootCheck` continues to run unchanged at db.init; the monitor is for long-running deployments where clock-drift can develop after boot (container with no RTC sync, ntpd stopped after boot, etc.). Closes the v0.7.79 audit-batch slice for continuous time integrity.
|
|
12
|
+
|
|
11
13
|
- **0.7.97** (2026-05-06) — `b.compliance` lookup helpers. **`b.compliance.posturesByDomain(domain)`** returns every posture matching the named domain (`"privacy"` / `"health"` / `"payment"` / `"cybersecurity"` / `"financial-reporting"` / `"financial-resilience"` / `"product-cybersecurity"` / `"ai-governance"` / `"biometrics"` / `"audit-attestation"`). **`b.compliance.posturesByJurisdiction(jurisdiction)`** returns postures matching the ISO 3166 alpha-2 code, `EU`, or `international` — useful for multi-region deployments that resolve different posture configs per region. **`b.compliance.list()`** returns every posture as a `{ posture, name, citation, jurisdiction, domain }` row in canonical `KNOWN_POSTURES` order — admin UIs render the full set as a dropdown / table without iterating REGIME_MAP keys themselves. All three helpers are pure functions over the v0.7.94 REGIME_MAP and stay in sync with it as new postures land.
|
|
12
14
|
|
|
13
15
|
- **0.7.96** (2026-05-06) — `b.observability.timed(name, fn, labels)` — convenience wrapper that measures wall-clock duration of a sync or async operation and emits a counter event carrying `duration_ms` in the labels alongside `outcome: "ok" | "fail"`. Returns the wrapped function's return value verbatim; rethrows on error after emitting the failure event with `error_type` capturing the thrown error's `.name`. Operators previously hand-rolled `var t0 = Date.now(); var r = await fn(); event(name, 1, { ...labels, duration_ms: Date.now() - t0, outcome: "ok" })` patterns at every call site — `timed` is the consolidated form. Composes with the existing `b.observability.SEMCONV` constants: `b.observability.timed("db.query", async () => db.query("SELECT ..."), { [SEMCONV.DB_OPERATION_NAME]: "select" })`. Operation name MUST be a stable string (not derived from input) to keep metric cardinality bounded; dynamic per-tenant labels go in the `labels` parameter.
|
package/lib/ntp-check.js
CHANGED
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
*/
|
|
28
28
|
var dgram = require("dgram");
|
|
29
29
|
var C = require("./constants");
|
|
30
|
+
var lazyRequire = require("./lazy-require");
|
|
31
|
+
var safeAsync = require("./safe-async");
|
|
32
|
+
|
|
33
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
34
|
+
var observability = lazyRequire(function () { return require("./observability"); });
|
|
30
35
|
|
|
31
36
|
// NTP epoch: 1900-01-01. Unix epoch: 1970-01-01. Offset: 70 years incl. 17
|
|
32
37
|
// leap days = 2,208,988,800 seconds.
|
|
@@ -227,10 +232,81 @@ async function bootCheck(opts) {
|
|
|
227
232
|
};
|
|
228
233
|
}
|
|
229
234
|
|
|
235
|
+
// Periodic drift monitor — runs checkDrift on a schedule and emits
|
|
236
|
+
// audit + observability events on threshold crossings. Returns a
|
|
237
|
+
// handle with `.stop()` for graceful shutdown.
|
|
238
|
+
//
|
|
239
|
+
// var mon = b.ntpCheck.monitor({
|
|
240
|
+
// intervalMs: C.TIME.minutes(15),
|
|
241
|
+
// servers: ["time.cloudflare.com", "pool.ntp.org"],
|
|
242
|
+
// driftWarnMs: C.TIME.seconds(2),
|
|
243
|
+
// driftFatalMs: C.TIME.seconds(30),
|
|
244
|
+
// onDrift: function (result) { /* operator hook — drift > warn */ },
|
|
245
|
+
// });
|
|
246
|
+
// ...
|
|
247
|
+
// await mon.stop();
|
|
248
|
+
//
|
|
249
|
+
// Audit emissions:
|
|
250
|
+
// system.ntp.checked — every check, success or fail
|
|
251
|
+
// system.ntp.drift_warn — drift exceeds warn threshold
|
|
252
|
+
// system.ntp.drift_fatal — drift exceeds fatal threshold
|
|
253
|
+
// system.ntp.unreachable — every server in the list failed to respond
|
|
254
|
+
//
|
|
255
|
+
// Observability events: ntp.drift_ms (gauge) on every successful check.
|
|
256
|
+
function monitor(opts) {
|
|
257
|
+
opts = opts || {};
|
|
258
|
+
var intervalMs = opts.intervalMs || C.TIME.minutes(15);
|
|
259
|
+
var auditOn = opts.audit !== false;
|
|
260
|
+
if (typeof intervalMs !== "number" || !isFinite(intervalMs) || intervalMs <= 0) {
|
|
261
|
+
throw new TypeError("ntpCheck.monitor: intervalMs must be a positive finite number");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function _emit(action, metadata) {
|
|
265
|
+
if (!auditOn) return;
|
|
266
|
+
try {
|
|
267
|
+
audit().safeEmit({
|
|
268
|
+
action: action,
|
|
269
|
+
outcome: metadata && metadata.severity === "fatal" ? "fail" : "ok",
|
|
270
|
+
metadata: metadata || {},
|
|
271
|
+
});
|
|
272
|
+
} catch (_e) { /* drop-silent */ }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function _tick() {
|
|
276
|
+
var res;
|
|
277
|
+
try { res = await bootCheck(opts); }
|
|
278
|
+
catch (e) {
|
|
279
|
+
_emit("system.ntp.checked", { severity: "fatal", error: (e && e.message) || String(e) });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (res.driftMs === null) {
|
|
283
|
+
_emit("system.ntp.unreachable", { severity: "warning", message: res.message });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
try { observability().safeEvent("ntp.drift_ms", res.driftMs, { server: res.server || "unknown" }); }
|
|
287
|
+
catch (_e) { /* drop-silent */ }
|
|
288
|
+
_emit("system.ntp.checked", { severity: res.severity, driftMs: res.driftMs, server: res.server });
|
|
289
|
+
if (res.severity === "fatal") {
|
|
290
|
+
_emit("system.ntp.drift_fatal", { driftMs: res.driftMs, server: res.server });
|
|
291
|
+
} else if (res.severity === "warning") {
|
|
292
|
+
_emit("system.ntp.drift_warn", { driftMs: res.driftMs, server: res.server });
|
|
293
|
+
}
|
|
294
|
+
if (typeof opts.onDrift === "function" && res.severity !== "info") {
|
|
295
|
+
try { await opts.onDrift(res); } catch (_e) { /* operator hook — drop-silent */ }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
var handle = safeAsync.repeating(_tick, intervalMs, { name: "ntp-monitor" });
|
|
300
|
+
return {
|
|
301
|
+
stop: function () { if (handle) { handle.stop(); handle = null; } },
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
230
305
|
module.exports = {
|
|
231
306
|
querySingle: querySingle,
|
|
232
307
|
checkDrift: checkDrift,
|
|
233
308
|
bootCheck: bootCheck,
|
|
309
|
+
monitor: monitor,
|
|
234
310
|
setThresholds: setThresholds,
|
|
235
311
|
getThresholds: getThresholds,
|
|
236
312
|
DEFAULT_SERVERS: DEFAULT_SERVERS,
|
package/package.json
CHANGED
package/sbom.cyclonedx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:387ca63b-9d87-46d5-a7a5-ff7e577e0bd3",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-06T09:03:18.727Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.7.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.7.98",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.98",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.7.98",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.7.
|
|
57
|
+
"ref": "@blamejs/core@0.7.98",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|