@blamejs/core 0.8.78 → 0.8.80

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,7 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.8.x
10
10
 
11
+ - v0.8.80 (2026-05-10) — **Bug fix — `b.config.loadDbBacked` overlapping-tick race**. `cfg.refresh()` calls `_tick()` directly and the periodic poller also invokes `_tick()` independently. When two ticks overlap (two `refresh()`es back-to-back, or `refresh()` racing a poll), the older read could resolve LAST and overwrite a newer config write — so `admin-save → await cfg.refresh()` was not guaranteed to leave the latest value active when `fetchRows` latency varied across calls. Reproducible by serving a 200ms read followed by a 20ms read; without the fix, the slower (older) result clobbered the faster (newer) one. Fix: every tick claims a monotonic sequence number at start; at apply-time, ticks whose sequence is older than the last-applied sequence drop with a `config.reload.skipped` audit emission (phase `stale-tick`). The high-water mark advances ONLY after `cfg.reload` succeeds — a newer tick whose validation fails must not suppress an older in-flight tick that still has valid data (otherwise `refresh(valid)` followed by `refresh(invalid)` could silently keep stale config active even though the valid update was about to land). Fetch / transform failures short-circuit before the apply path and likewise do NOT advance the watermark.
11
12
  - v0.8.78 (2026-05-10) — save-triggered reload for `b.config.loadDbBacked`. Admin save handlers / settings-management UIs that write a row in `_blamejs_config_overrides` now call `await cfg.refresh()` immediately after the write, so the new value is active without waiting for the poll's `intervalMs` tick. The poll stays in place as a safety-net for drift (e.g., direct DB writes outside the admin path). `refresh()` returns a `Promise<void>` of identical shape to `cfg.hydrated`: resolves after the tick settles (success OR audit-on-failure), NEVER rejects so save handlers don't deadlock on a flaky DB. The existing `cfg.subscribe(fn)` continues to fire synchronously inside every successful reload — operators reach for it to invalidate caches / recompute derived state / hot-rebuild middleware that closed over the previous config. Three-tier precedence is documented explicitly in the `@primitive` block: DB-row overlay > `opts.env` baseline > schema `default(...)`.
12
13
  - v0.8.77 (2026-05-10) — substantive additive release closing 10 audit clusters surfaced by the 8-agent compliance audit. **OAuth resource-server completeness**: `b.auth.oauth.introspectToken` (RFC 7662), `registerClient` (RFC 7591 — refuses empty redirect_uris), `deviceAuthorization` + `pollDeviceCode` (RFC 8628 with slow_down/authorization_pending handling), `exchangeToken` (RFC 8693 subject+actor delegation), new `b.middleware.protectedResourceMetadata` serving `.well-known/oauth-protected-resource` (draft-ietf-oauth-resource-metadata). **Vendored-deps SBOM**: new `scripts/build-vendored-sbom.js` emits `sbom.vendored.cdx.json` (CycloneDX 1.6) covering every `lib/vendor/*` bundle with per-file SHA-256 + purl + license metadata; wired into `npm-publish.yml` so OSV-Scanner now scans it alongside the primary `sbom.cdx.json` — closes the gap where downstream scanners couldn't see what was actually shipping. **MCP endpoint coverage**: `b.mcp.assertProtocolVersion` (MCP 2025-11-25 §4.1 header), `b.mcp.sampling.guard({ maxRequestsPerSession, maxMessagesPerRequest, maxTokensPerRequest, allowedModelHints })` (HIGH-RISK endpoint — confused-deputy class), `b.mcp.elicitation.guard` (prompt-injection scan + schema-type allowlist + size cap). **ACME completeness**: `revokeCert` (RFC 8555 §7.6), `accountKeyRollover` (§7.3.5), `deactivateAccount` (§7.3.6), `tlsAlpn01KeyAuthorization` (RFC 8737), External Account Binding opt on `newAccount` (§7.3.4 — required by ZeroSSL/Buypass/Google CA) — closes 47-day CA/B forum surface before Mar 2026 effective date. **Permissions-Policy denylist** expanded with `identity-credentials-get`, `attribution-reporting-cross-site`, `publickey-credentials-create`, `join-ad-interest-group`, `run-ad-auction`, `shared-storage`, `shared-storage-select-url`, `smartcard`, `all-screens-capture`, `deferred-fetch` (10 directives — single-file fix). **NIST control crosswalk**: new `b.nistCrosswalk` catalog mapping `800-53r5` (~50 controls), `csf-2.0` (~22 functions), `800-171r3` (~25 requirements), `800-218` (SSDF tasks) to framework primitives — used by operators producing SSPs, POAMs, ATO packages, CMMC self-assessments. **SCIM 2.0 server**: new `b.middleware.scimServer` implementing RFC 7642/7643/7644 — Users + Groups + ServiceProviderConfig + ResourceTypes + Schemas + filter parser (eq/ne/co/sw/ew/pr/gt/ge/lt/le) + GET/POST/PUT/PATCH/DELETE dispatch + bearer-auth callback hook + 1 MiB body cap; the most operator-visible federation gap before this — Okta/Entra/etc. couldn't push users without an external adapter. **CRA + EU AI Act forward-deadline templates**: `b.cra.conformityAssessment` Annex VIII technical dossier scaffold (CE marking, Module routing, vuln-handling auto-fill), `b.complianceAiAct.fundamentalRightsImpactAssessment` (Article 27 FRIA template — mandatory for Annex III §5-8 deployers), `b.complianceAiAct.gpai.trainingDataSummary` (Article 53(1)(d) AI Office template — mandatory 2026-08-02). **C2PA COSE_Sign1 wrap**: new `b.contentCredentials.signCose` produces RFC 9052 COSE_Sign1 CBOR envelope with x5chain header + ML-DSA-87 / ed25519 / es256/384/512 / SLH-DSA-SHAKE-256f algorithms — interops with c2patool / JPEG Trust / Adobe verifiers (current `sign()` ships a blamejs-internal envelope; the new `signCose()` ships the canonical wire format). **US state-law backlog**: 22 new compliance postures (`vcdpa`, `co-cpa`, `ctdpa`, `ucpa`, `tdpsa`, `or-cpa`, `mt-cdpa`, `ia-icdpa`, `in-indpa`, `de-dpdpa`, `nh-nhpa`, `nj-njdpa`, `ky-kcdpa`, `tn-tipa`, `mn-mncdpa`, `ri-ricpa`, `ne-dpa`, `nv-sb370`, `ca-aadc`, `ct-sb3`, `tx-cubi`, plus existing `modpa` + `quebec-25`) registered in `b.compliance` + per-state DSR rules via `b.dsr.stateRules(state)` / `b.dsr.listStateRules()` returning `{ responseDays, extensionDays, cureDays, profilingOptOut, minorOptIn, notes }`. **Operator hook**: `b.middleware.rateLimit` instance gains `.resetAll()` for clean-slate flushing during incident-response (in-memory backends only; cluster backend no-ops per multi-replica race-safety). Cluster backend correctly refuses lest one replica's flush race another's in-flight `take()`. **`b.config.loadDbBacked` gains `transformValue: (row) => string | Promise<string>`** — per-row transform applied between `fetchRows` and schema validation; common shape is unsealing a `b.vault`-sealed ciphertext column so canonical secrets live encrypted-at-rest in `_blamejs_config_overrides`. Per-row failures (transform throws OR returns non-string) emit `config.reload.failed` and skip the row so a single bad row can't crash the poller. **`b.cryptoField` gains `sealDoc` / `unsealDoc` doc-shaped aliases** of the existing `sealRow` / `unsealRow` — same identity, lets downstream tests reach for the document-naming convention when preparing seed objects via raw `INSERT`. **Bug fix — `b.config` reactive `value`**: `cfg.value.X` now reflects the latest validated state after every `reload()` (and every `loadDbBacked` poll). Before this fix, `cfg.value` was a captured property pinned to the create-time object, so `cfg.value.FEATURE_X` stayed stale forever and only `cfg.get("FEATURE_X")` saw updates — the published example in `@primitive b.config.loadDbBacked` was wrong against the implementation. Now backed by a `Object.defineProperty` getter; `cfg.get()` / `cfg.has()` semantics unchanged. **Bug fix — `b.config.loadDbBacked` startup hydration window**: `loadDbBacked` returned a config handle that stayed at env-only defaults for the first `intervalMs` because `safeAsync.repeating` is `setInterval`-shaped (no t=0 fire). The handle now kicks off one immediate hydration `_tick()` on construction and exposes `cfg.hydrated` — a Promise that resolves after the first tick settles. Callers awaiting it before serving traffic get a fully-hydrated config; the Promise NEVER rejects (per-tick failures route through audit, last-good value stays). **`b.middleware._modules.rateLimit.instances()` + module-level `.resetAll()`** — module now keeps a registry of every rate-limit middleware created in the process. Incident-response scripts can enumerate every limiter and flush state across the whole process without threading references through the app code. `create()` registers; `middleware.close()` deregisters. Top-level `resetAll()` returns the count of instances it walked.
13
14
  - v0.8.76 (2026-05-10) — CI green-up for v0.8.75. The OSV-Scanner v2 binary refuses to parse SBOMs whose filename doesn't match the CycloneDX recognized-pattern spec — `sbom.cyclonedx.json` is NOT recognized; only `bom.json` / `*.cdx.json` / `*.spdx.json` etc. are. v0.8.75's npm-publish workflow failed with `Failed to parse SBOM "sbom.cyclonedx.json": Invalid SBOM filename`. Renamed the artifact to `sbom.cdx.json` everywhere (workflow generation step, post-process script, OSV scan target, cosign sign target, GH release asset upload, `package.json` `files` array, `scripts/check-pack-against-gitignore.js` allowlist, `.gitignore` allowlist). No primitive surface change versus v0.8.75; published-tarball asset filename changes from `sbom.cyclonedx.json` to `sbom.cdx.json` (consumers reading the SBOM out of the install tree should update the path).
package/lib/config.js CHANGED
@@ -324,8 +324,27 @@ function loadDbBacked(opts) {
324
324
  ConfigError, "config/bad-transform-value") || null;
325
325
  var cfg = create({ schema: opts.schema, env: opts.env, redactKeys: opts.redactKeys });
326
326
  var stopped = false;
327
+ // Concurrency guard. _tick() runs `await opts.fetchRows()` + per-row
328
+ // `await transformValue(row)`, so multiple ticks (poll firing while
329
+ // refresh() is in-flight, or two refresh()es back-to-back) can
330
+ // overlap. Without coordination, whichever tick FINISHES last applies
331
+ // its overlay last — and "finishes last" is not "started last" when
332
+ // fetchRows latency varies. The result: an admin save followed by
333
+ // await refresh() can be silently rolled back by an older in-flight
334
+ // tick whose fetchRows started before the save.
335
+ //
336
+ // Fix: every tick claims a monotonic seq at start. At apply time, if
337
+ // a newer tick has already applied (ticksAppliedMax >= my seq), drop
338
+ // — its data is more recent than mine. The seq check + reload are
339
+ // both synchronous (no awaits between them) so the check-and-apply
340
+ // is atomic on Node's single thread. fetch / transform failures do
341
+ // NOT advance ticksAppliedMax: they short-circuit before the apply
342
+ // path, leaving newer ticks free to apply later.
343
+ var ticksStarted = 0;
344
+ var ticksAppliedMax = -1;
327
345
  async function _tick() {
328
346
  if (stopped) return;
347
+ var mySeq = ++ticksStarted;
329
348
  var rows;
330
349
  try { rows = await opts.fetchRows(); }
331
350
  catch (e) {
@@ -367,7 +386,26 @@ function loadDbBacked(opts) {
367
386
  }
368
387
  overlay[row.key] = value;
369
388
  }
370
- try { cfg.reload(overlay); }
389
+ // Drop-stale: a tick that started after me has already finished and
390
+ // applied its newer fetch — my overlay would clobber fresher data.
391
+ if (mySeq <= ticksAppliedMax) {
392
+ try {
393
+ lazyAudit().safeEmit({
394
+ action: "config.reload.skipped", outcome: "success",
395
+ metadata: { phase: "stale-tick", mySeq: mySeq, appliedMax: ticksAppliedMax },
396
+ });
397
+ } catch (_e) { /* audit best-effort */ }
398
+ return;
399
+ }
400
+ // Advance the watermark ONLY after a successful reload. A newer
401
+ // tick whose validation fails must not suppress an older in-flight
402
+ // tick that still has valid data — otherwise refresh(valid)
403
+ // followed by refresh(invalid) could silently keep the previous
404
+ // config active even though the valid update is about to land.
405
+ try {
406
+ cfg.reload(overlay);
407
+ ticksAppliedMax = mySeq;
408
+ }
371
409
  catch (e) {
372
410
  try {
373
411
  lazyAudit().safeEmit({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.8.78",
3
+ "version": "0.8.80",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.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.6",
5
- "serialNumber": "urn:uuid:77cdedbf-1ccf-45c3-afec-2d0261ed59ce",
5
+ "serialNumber": "urn:uuid:34412b93-1297-4f78-8272-084d2928e90a",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-11T03:48:18.757Z",
8
+ "timestamp": "2026-05-11T05:57:08.754Z",
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.8.78",
22
+ "bom-ref": "@blamejs/core@0.8.80",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.8.78",
25
+ "version": "0.8.80",
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.8.78",
29
+ "purl": "pkg:npm/%40blamejs/core@0.8.80",
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.8.78",
57
+ "ref": "@blamejs/core@0.8.80",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]