@blamejs/core 0.7.96 → 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,10 @@ 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
+
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.
14
+
11
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.
12
16
 
13
17
  - **0.7.95** (2026-05-06) — `b.observability.SEMCONV` GenAI + cloud + container coverage. Twenty-eight new attribute names tracking OpenTelemetry semantic conventions for generative AI workloads, vector databases, cloud runtime context, and Kubernetes orchestration. **GenAI:** `GEN_AI_SYSTEM` / `GEN_AI_REQUEST_MODEL` / `GEN_AI_REQUEST_TEMPERATURE` / `GEN_AI_REQUEST_TOP_P` / `GEN_AI_REQUEST_TOP_K` / `GEN_AI_REQUEST_MAX_TOKENS` / `GEN_AI_REQUEST_STOP_SEQUENCES` / `GEN_AI_RESPONSE_MODEL` / `GEN_AI_RESPONSE_ID` / `GEN_AI_RESPONSE_FINISH_REASONS` / `GEN_AI_USAGE_INPUT_TOKENS` / `GEN_AI_USAGE_OUTPUT_TOKENS` / `GEN_AI_USAGE_TOTAL_TOKENS` / `GEN_AI_OPERATION_NAME` / `GEN_AI_TOOL_NAME` / `GEN_AI_TOOL_CALL_ID` / `GEN_AI_AGENT_ID` / `GEN_AI_AGENT_NAME` / `GEN_AI_AGENT_DESCRIPTION`. **Vector DB / RAG:** `DB_VECTOR_QUERY_TOP_K` / `DB_VECTOR_QUERY_DIMENSIONS` / `DB_VECTOR_QUERY_DISTANCE_METRIC`. **Cloud:** `CLOUD_PROVIDER` / `CLOUD_REGION` / `CLOUD_ACCOUNT_ID` / `CLOUD_RESOURCE_ID`. **Container / K8s:** `CONTAINER_ID` / `CONTAINER_IMAGE_NAME` / `CONTAINER_IMAGE_TAG` / `K8S_NAMESPACE_NAME` / `K8S_POD_NAME` / `K8S_DEPLOYMENT_NAME`. Operators wiring LLM client telemetry, vector-DB query traces, or Kubernetes-deployed services to an OTel collector reference these constants directly — string typos throw at access time instead of producing mis-named span attributes the OTel collector silently drops.
package/lib/compliance.js CHANGED
@@ -245,14 +245,68 @@ function describe(posture) {
245
245
  return REGIME_MAP[posture] || null;
246
246
  }
247
247
 
248
+ // posturesByDomain — list every posture that maps to the named
249
+ // domain (privacy / health / payment / cybersecurity / etc.).
250
+ // Operators rendering compliance dashboards grouped by domain pull
251
+ // the per-domain posture list with this; admin UIs that show "we
252
+ // satisfy the privacy regimes for {users.country}" use it to pick
253
+ // the right posture name without hand-rolling the lookup.
254
+ function posturesByDomain(domain) {
255
+ if (typeof domain !== "string" || domain.length === 0) return [];
256
+ var out = [];
257
+ var keys = Object.keys(REGIME_MAP);
258
+ for (var i = 0; i < keys.length; i++) {
259
+ if (REGIME_MAP[keys[i]].domain === domain) out.push(keys[i]);
260
+ }
261
+ return out;
262
+ }
263
+
264
+ // posturesByJurisdiction — same shape, keyed off the ISO 3166 alpha-2
265
+ // code or `EU` / `international`. Operators handling a multi-region
266
+ // deployment (e.g. one that serves users in EU + CA + JP) iterate
267
+ // over jurisdiction codes and resolve to per-jurisdiction posture
268
+ // configs without hand-rolling the lookup table.
269
+ function posturesByJurisdiction(jurisdiction) {
270
+ if (typeof jurisdiction !== "string" || jurisdiction.length === 0) return [];
271
+ var out = [];
272
+ var keys = Object.keys(REGIME_MAP);
273
+ for (var i = 0; i < keys.length; i++) {
274
+ if (REGIME_MAP[keys[i]].jurisdiction === jurisdiction) out.push(keys[i]);
275
+ }
276
+ return out;
277
+ }
278
+
279
+ // list — returns every posture as a { name, ...regime-map-fields }
280
+ // object array, in canonical KNOWN_POSTURES order. Useful for admin
281
+ // UIs that render the full set as a dropdown / table.
282
+ function list() {
283
+ var out = [];
284
+ for (var i = 0; i < KNOWN_POSTURES.length; i++) {
285
+ var p = KNOWN_POSTURES[i];
286
+ var meta = REGIME_MAP[p];
287
+ if (!meta) continue;
288
+ out.push({
289
+ posture: p,
290
+ name: meta.name,
291
+ citation: meta.citation,
292
+ jurisdiction: meta.jurisdiction,
293
+ domain: meta.domain,
294
+ });
295
+ }
296
+ return out;
297
+ }
298
+
248
299
  module.exports = {
249
- set: set,
250
- current: current,
251
- assert: assert,
252
- clear: clear,
253
- describe: describe,
254
- KNOWN_POSTURES: KNOWN_POSTURES,
255
- REGIME_MAP: REGIME_MAP,
256
- ComplianceError: ComplianceError,
257
- _resetForTest: _resetForTest,
300
+ set: set,
301
+ current: current,
302
+ assert: assert,
303
+ clear: clear,
304
+ describe: describe,
305
+ posturesByDomain: posturesByDomain,
306
+ posturesByJurisdiction: posturesByJurisdiction,
307
+ list: list,
308
+ KNOWN_POSTURES: KNOWN_POSTURES,
309
+ REGIME_MAP: REGIME_MAP,
310
+ ComplianceError: ComplianceError,
311
+ _resetForTest: _resetForTest,
258
312
  };
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.96",
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:f930dea4-02bf-424a-81d9-af05bce63d00",
5
+ "serialNumber": "urn:uuid:387ca63b-9d87-46d5-a7a5-ff7e577e0bd3",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-06T08:44:44.540Z",
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.96",
22
+ "bom-ref": "@blamejs/core@0.7.98",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.7.96",
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.96",
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.96",
57
+ "ref": "@blamejs/core@0.7.98",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]