@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.7.97",
3
+ "version": "0.7.98",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -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:55086519-816f-48aa-8cc1-ab945665d119",
5
+ "serialNumber": "urn:uuid:387ca63b-9d87-46d5-a7a5-ff7e577e0bd3",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-06T08:53:19.996Z",
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.97",
22
+ "bom-ref": "@blamejs/core@0.7.98",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.7.97",
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.97",
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.97",
57
+ "ref": "@blamejs/core@0.7.98",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]