@blamejs/core 0.18.4 → 0.18.5
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/package.json +1 -1
- package/sbom.cdx.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.18.x
|
|
10
10
|
|
|
11
|
+
- v0.18.5 (2026-08-01) — **Error- and edge-path tests for the agent orchestrator, mTLS request gate, and i18n primitives.** Error, edge, and adversarial branches of b.agent.orchestrator (registry hydrate/register validation, consumer spawn and start lifecycle, cluster election cache and leader probes, drain and quiesce budgets, tenant-scope authorization, per-row reseal), the b.middleware.requireMtls request gate (no-peer-cert and fingerprint-failure refusals, allow/deny-list gates, revocation-source and onAuthenticated error paths, audit outcomes), and b.i18n's translation-tree and file-load edges are now under test. Behaviour is unchanged -- these pin guarantees the primitives already met. **Changed:** *Error- and edge-path tests for the agent orchestrator, mTLS request gate, and i18n primitives* — b.agent.orchestrator (registry hydrate/register non-object refusals, consumer spawn missing-agent/missing-queue refusals and the shard-topic naming, consumer start double-start refusal and the direct / dotted-nested / unknown consume-handler method resolution, cluster onTransition election-cache clear, elect argument validation and the election cache-hit path, currentLeader/isLeader/fencingToken error handling, drain timeout and the quiesce poll budget, tenant-scope actor-missing / no-tenant authorization, and reseal store-validation with per-cell AAD-sealed filtering), the b.middleware.requireMtls gate (no-opts default, allow/deny-list normalization and gate outcomes, the connection fallback and null-peer-cert / no-raw / fingerprint-failed refusals, deny-match refusal, the revocationSource and onAuthenticated error paths including a non-Error rejection, and audit allowed/refused outcomes), and b.i18n's translation-tree validation and file-load edge branches now have explicit tests for their error and boundary paths. Behaviour is unchanged -- the additions pin guarantees the primitives already met.
|
|
12
|
+
|
|
11
13
|
- v0.18.4 (2026-08-01) — **`b.session.updateData({ merge: true })` now merges an inner object one level deep instead of discarding the existing nested keys, alongside error- and edge-path tests for the queue, encrypted-field, session, and CalDAV/CardDAV primitives.** b.session.updateData(token, data, { merge: true }) documents a one-level-deep merge, but an inner object value shallow-replaced the whole nested object -- so updating one nested key silently dropped the operator's other nested keys. It now merges an inner plain object into the existing one (its keys survive); an array or non-object value still replaces. Separately, error, edge, and adversarial branches of b.queue (init and backend resolution, consume success/retry/dead-letter, enqueueFlow validation and dependency ordering, dead-letter list/retry/size, shutdown drain, bootFromEnv), b.cryptoField (seal/unseal envelope-floor enforcement, derived-hash modes, per-row-key unseal rate cooldown, data residency, read-time hash upgrade, erase-row vacuum cascade, hash lookup), b.session (fingerprint-drift scoring and fail-closed binding, anonymous mint, rotate carry-forward and re-key, updateData merge guards, validFrom scheduling), and b.mail.dav (CalDAV/CardDAV request and response branches) are now under test. **Changed:** *Error- and edge-path tests for the queue, encrypted-field, session, and CalDAV/CardDAV primitives* — b.queue (init idempotency and the missing-backends / unknown-backend refusals, consume success/retry/dead-letter-write/rate-limit-wait/lease-extend/progress paths, enqueueFlow validation and cycle/dependency ordering, dead-letter list/retry/size with the SQS-unsupported refusals, shutdown pre-init no-op and in-flight drain, bootFromEnv config resolution), b.cryptoField (seal-envelope-floor enforcement, derived-hash keyed/legacy modes and truncation, per-row-key unseal rate cooldown and its audit, data residency, read-time derived-hash upgrade, the erase-row vacuum cascade, and hash lookup), b.session (fingerprint-drift scoring with clamp and fail-closed admission, strict-binding missing/unreadable refusal, anonymous mint and the anonymous-plus-userId refusal, rotate data-replace / carry-forward / bound re-key, updateData token and merge guards, and validFrom read-back with check admit/revoke), and b.mail.dav's request and response branches now have explicit tests for their error and boundary paths. Behaviour is unchanged for these -- the additions pin guarantees the primitives already met. · *Vendored @blamejs/pki refreshed to 0.3.28* — The bundled PKI toolkit (lib/vendor/blamejs-pki.cjs, the mTLS CA engine's certificate/CMS backend) is refreshed from 0.3.27 to 0.3.28 so the vendored copy matches the latest published release. It adds no runtime dependency and no public framework API; the upstream release adds HTTP Digest authentication (RFC 7616) to the EST client transport. **Fixed:** *b.session.updateData({ merge: true }) merges an inner object one level deep instead of discarding existing nested keys* — updateData(token, data, { merge: true }) is documented to merge one level deep -- inner objects merge, arrays replace. It merged only the top-level keys: an inner object value was assigned wholesale (next[k] = data[k]), so updateData(token, { prefs: { theme: "dark" } }, { merge: true }) against a stored { prefs: { locale: "en" } } dropped prefs.locale, leaving only { prefs: { theme: "dark" } }. An inner PLAIN-object value now merges into the existing plain object (its keys survive, then the incoming keys apply); an array, a non-plain object (a Date, Buffer, or class instance -- stored as its own JSON form rather than mangled into the retained old object or byte-index keys), or a non-object value replaces, and merging beyond one level replaces. Operators who relied on the previous top-level-only behaviour, or who intend to replace a whole nested object, can pass the full nested value they want stored. **Detectors:** *session.updateData({ merge: true }) must merge an inner object one level deep* — A source check asserts the merge path merges an inner value only when BOTH are PLAIN objects (a prototype-based _isPlainObject) as Object.assign({}, existing[k], data[k]), never a bare next[k] = data[k] shallow replace, so the nested-key data-loss regression -- and a Date/Buffer being merged instead of replaced -- cannot return unnoticed.
|
|
12
14
|
|
|
13
15
|
- v0.18.3 (2026-07-30) — **`b.mtlsCa` gains non-breaking CA algorithm-migration primitives -- rotate the CA, keep the superseded root trusted during re-enrollment, self-test that the runtime verifies the algorithm over TLS, and revoke by CA generation -- and `b.db.collection`'s `$like` operator now honours SQL `%` / `_` wildcards instead of matching them as literal characters.** b.mtlsCa can now migrate a certificate authority to a new signature algorithm without an outage. status() reports the stored CA's algorithm and key type; rotate({ generation, algorithm }) generates and atomically commits a new CA and returns the superseded certificate, without the algorithm-mismatch initCA() raises once a CA already exists; await commit({ retainPrevious: true }) (the public commit is the locked primitive — it resolves to a promise, takes the rotation lock so a direct caller cannot race a concurrent rotate/init, and refreshes the handle's algorithm pin to the committed CA like rotate() so a pinned handle that migrates algorithms via commit() stays usable) together with await loadTrustBundle() and dropRetained() keep the old root trusted through a re-enrollment grace window; canVerifyInTls() runs a loopback mutual-TLS self-test proving node:tls verifies the CA's algorithm on the running Node; and revokeGeneration(n) revokes every certificate the issuance ledger recorded under a CA generation below n. Separately, b.db.collection's $like operator emitted an escaped LIKE, so the caller's % and _ were matched as literal characters and $like collapsed to an exact match; it now emits a caller-controlled-wildcard LIKE, so find({ name: { $like: "Al%" } }) matches as SQL LIKE documents. The release also brings error and adversarial branches of the scheduler, session device-binding, body parser, notifier, and document collection under test. **Added:** *b.mtlsCa non-breaking CA algorithm-migration primitives* — A CA handle can now carry an operator through a signature-algorithm migration (for example a classical-to-post-quantum move) with no trust outage. status() reports the stored CA's algorithm and keyType (null before initCA(), and null for an EC CA whose curve is not the framework's P-384 OR whose signature digest is not SHA-384, so a custom P-256/P-521 or SHA-256/SHA-512-signed CA is not mislabeled ECDSA-P384-SHA384). An unpinned rotate({ generation }) preserves the stored CA's algorithm (it does not silently adopt the engine's post-quantum default), so advancing a generation on a classical ECDSA CA keeps issuing classical chains; changing algorithm stays explicit via rotate({ algorithm }). rotate({ generation, algorithm }) generates a new CA at a strictly higher integer generation and atomically commits it -- returning { caCertPem, previousCaCertPem, generation, algorithm } -- so an operator does not hit the algorithm-mismatch initCA() raises when a CA is already present, and the handle keeps issuing under the new algorithm afterward. A rotation also invalidates a persisted CRL (it was signed by the superseded CA), so regenerate it under the new CA with generateCrl(). The commit path gained retainPrevious (defaulted on for rotate): the outgoing certificate is snapshotted to ca.prev.crt, and await loadTrustBundle() returns [current, ...retained] so both roots are trusted while clients re-enroll (it resolves to a promise, taking the rotation lock so its snapshot is serialized with rotation and dropRetained()); dropRetained(), or rotate({ retainPrevious: false }), ends that window. There is one retained window at a time: a second retained rotation while a root is still retained is refused (mtls-ca/retained-root-exists) rather than silently overwriting the retained root and rejecting clients still enrolled under it -- end the window first, and a plain commit() that omits retainPrevious while a window is open is likewise refused (mtls-ca/retention-intent-required) rather than replacing the active CA while leaving the old root, which would drop trust for the just-superseded generation. A rotation also aborts if it cannot capture the existing key for its rollback journal, and loadTrustBundle() still returns a retained root a crashed rotation left only in that journal. canVerifyInTls(algorithm?) performs a loopback mutual-TLS handshake with a freshly issued leaf and resolves true only if node:tls actually authenticates the client against a CA on the running runtime -- pass the prospective algorithm to pre-flight a migration (canVerifyInTls("ML-DSA-87") probes the target chain, not the current CA) before rotating to an algorithm the deployed Node cannot verify; with no argument it probes the stored CA -- and requires an explicit algorithm when the stored CA's algorithm cannot be derived (a custom-engine CA this runtime does not classify, with no create-time pin), rather than probing an undeterminable one that the engine could resolve to a different algorithm. revokeGeneration(n) walks an issuance ledger (recorded automatically as certificates are issued, or supplied via the new issuanceStore option) and revokes every certificate issued under a CA generation below n -- with an undeterminable generation (a custom engine whose CA certificate this runtime cannot parse, such as an opaque post-quantum cert) recorded as null and skipped by that sweep, still revocable by serial or fingerprint, rather than misfiled as generation 0 (which would sweep current leaves and self-revoke future issuances); issuance fails closed if the ledger cannot be written, so no untracked credential escapes revocation, and importIssuance(entries) backfills leaf identities the ledger does not have (certificates from a pre-upgrade dataDir or issued out of band) so revokeGeneration can sweep them too. Rotation is crash-atomic: because the CA key, certificate, and retained root are separate files that cannot be swapped in a single rename, a rotation that is interrupted between them (a crash or power loss) is reconciled the next time the CA is opened -- the prior key AND the prior retained root are journaled durably before either is overwritten, so an interrupted rotation rolls back to the still-usable previous CA (and the trust bundle its retained root still serves) instead of stranding it as an unrecoverable new-key/old-certificate pair or dropping trust for clients enrolled under the formerly-retained generation. A rollback journal that is unreadable or is not a valid manifest fails the open closed (mtls-ca/rollback-journal-corrupt) so an unresolved rotation marker is never overwritten, rather than being silently discarded and losing the prior key. Establishing the requested retention is part of the commit: a rotation that cannot write the retained root (for example an unwritable ca.prev.crt directory) aborts and rolls back rather than publishing a new CA that silently omits the outgoing root, and a rotate({ retainPrevious: false }) that cannot remove the old root aborts rather than leaving a root the operator asked to hard-cut still trusted. The engine interface adds canVerifyInTls(algorithm) for pluggable engines. · *b.middleware.requireMtls enforces a live revocation source* — requireMtls({ revocationSource: caHandle }) consults a b.mtlsCa handle (or any { isRevoked(fingerprintHex) } object) on every request, refusing a peer whose fingerprint the CA reports revoked. This enforces revoke() and revokeGeneration() at the gate without mirroring the registry into denyList, and it is CA-generation-independent (fingerprint-keyed) -- so a revoked superseded-generation cert is refused even though a CRL signed by the new CA could not cover the old cohort. The lookup fails closed: a source that throws refuses the request. **Changed:** *b.mtlsCa revoke() and revokeGeneration() return a promise* — The revocation registry's read-modify-write now runs under a cross-process file lock so concurrent revocations against the same data directory cannot lose each other's entry. b.mtlsCa revoke() -- previously synchronous -- and revokeGeneration() therefore return a promise resolving to their result; input validation still throws synchronously. Callers must await them: await ca.revoke(serial) / const { revoked } = await ca.revokeGeneration(n). isRevoked() stays synchronous and now reads an in-memory index kept fresh against the store, so a revocationSource-wired require-mtls gate does O(1) per-request lookups. An issuance whose signing straddles a rotate()+revokeGeneration() for its own generation -- or a rotate({ retainPrevious: false }) / dropRetained() that removes the root it was signed under -- is now refused (mtls-ca/issuance-superseded) rather than returned as a credential the sweep missed or a leaf that chains to a root no longer trusted; a clustered revocation store (shared across hosts with per-host dataDirs) can share that supersede watermark by additionally exposing { readGenerationWatermark(), bumpGenerationWatermark(n) }. A clustered revocation store now also requires a shared issuanceStore -- constructing one with the default per-host issuance ledger is refused, since revokeGeneration() would otherwise miss certificates issued on another host and leave them accepted by the shared revocation gate. loadTrustBundle() reads a stable snapshot of the current certificate (re-reading and retrying if a retained rotation publishes mid-read), so it never returns a bundle that omits the newly active root, and it deduplicates an identical retained root. · *Error- and edge-path tests for the scheduler, session device-binding, body parser, notifier, and document collection* — The scheduler's register / schedule validation, start / stop / status lifecycle, and task fire-and-failure recording; b.session device-binding storage and its store-error path; the body parser across its supported content types; the notifier's transport surface; and b.db.collection's query operators now have explicit tests for their error and boundary branches. Behaviour is unchanged -- these pin guarantees the primitives already met. · *Vendored @blamejs/pki refreshed to 0.3.27* — The bundled PKI toolkit (lib/vendor/blamejs-pki.cjs, the mTLS CA engine's certificate/CMS backend) is refreshed from 0.3.25 to 0.3.27. It adds no runtime dependency and no public framework API; the upstream releases add CMP certificate-enrollment protocol support (pki.cmp.verify checks a PKIMessage's signature/PBMAC1 protection; pki.cmp.session drives a full RFC 9810 ir/cr/kur/p10cr enrollment exchange) and fixes across the X.509 / CMS / PKCS#12 surface (subjectAltName IP-address string GeneralNames, RSASSA-PKCS1-v1_5 WebCrypto algorithm-name casing, public-key-integrity PKCS#12, CMS countersignature verification, cross-origin SNI/pin retention in the EST/ACME clients). **Fixed:** *b.db.collection $like honours SQL % / _ wildcards instead of matching them literally* — find({ field: { $like: "Al%" } }) routed the pattern through the query builder's escaping LIKE comparator, which escapes % and _ so a bound value cannot smuggle wildcards. For $like -- whose contract is a SQL LIKE pattern -- that neutralised the caller's wildcards and collapsed the operator to an exact match ($like: "Al%" matched only the literal string "Al%", never "Alice"). $like now emits a caller-controlled-wildcard LIKE, so %, _, prefix, suffix, infix, and single-character patterns all resolve as SQL LIKE defines. Exact patterns with no wildcard are unchanged. Callers that relied on the previous literal-match behaviour should pass the field value directly ({ field: value }) or escape their own wildcards. · *b.mtlsCa fails closed on a schema-corrupt issuance ledger instead of silently emptying it* — A present issuance ledger that was valid JSON but carried no `issued` array (an accidental `{}`, or a truncated / externally-rewritten file) was treated as an empty ledger. The next issuance then rewrote the file with only its own entry, permanently dropping every prior certificate from the sole index revokeGeneration() consults -- so those certificates would survive a later generation revocation. A present ledger with a missing or non-array `issued` now fails closed (mtls-ca/issuance-corrupt), exactly as malformed JSON already did; the operator restores or removes the file. An absent ledger still reads as empty. **Detectors:** *b.db.collection $like must use the verbatim caller-wildcard LIKE* — A source check asserts the collection's $like operator routes through the caller-controlled-wildcard LIKE and never the value-escaping LIKE comparator, so the wildcard-escaping regression cannot return unnoticed. · *b.mtlsCa issuance fingerprint must hash the certificate DER* — A source check asserts the issued-certificate fingerprint is computed as the DER hash the require-mtls gate pins, not the PEM-text hash -- a mismatch would silently make revoke() / revokeGeneration() by fingerprint unenforceable at the gate. · *b.mtlsCa CA rotation must journal the prior key before overwriting it* — A source check asserts the commit path persists a durable rollback copy of the prior CA key before the rename that overwrites it, and that both the read and the rotate paths reconcile a leftover journal on open -- so an interrupted rotation can never regress to leaving the new key beside the old certificate with the prior key lost.
|
package/package.json
CHANGED
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.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:c76abfd3-3a0a-4021-9a10-644df6ec17a3",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-01T22:29:48.972Z",
|
|
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.18.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.18.5",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.18.
|
|
25
|
+
"version": "0.18.5",
|
|
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.18.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.18.5",
|
|
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.18.
|
|
57
|
+
"ref": "@blamejs/core@0.18.5",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|