@blamejs/core 0.15.27 → 0.15.28
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/network-dns.js +28 -1
- package/lib/network-tls.js +21 -1
- 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.15.x
|
|
10
10
|
|
|
11
|
+
- v0.15.28 (2026-06-25) — **`b.network.dns` and `b.network.tls` errors now carry a usable terminal-vs-transient signal on `err.permanent`, so a caller's retry loop re-attempts only the failures a retry can fix.** DnsError was declared always-transient and NetworkTlsError always-permanent, so a consumer driving its own retry loop got the wrong signal in both directions: it would retry a permanent DNS failure (a bad host, bad options, an unsupported query, an NXDOMAIN-style no-result, or a caller-shape/config error such as an unconfigured transport or an invalid resolver list) forever, and it would refuse to retry a transient TLS-over-network failure (an ECH connection failure, a handshake timeout, DNS momentarily unavailable). err.permanent now reflects each error's actual transience, derived from its code and failing closed (an unknown code is permanent, so a hopeless target is not retried indefinitely): for DnsError only a failed network round-trip (a lookup timeout, a resolve/reverse query that failed on the wire, a failed DoH/DoT exchange, a DDR discovery that found nothing) is transient — config, input, and environment errors raised before any network work are permanent; for NetworkTlsError only the network-layer ECH failures are transient. TlsTrustError remains always-permanent by design — a trust-verification failure (bad CA, fingerprint mismatch, OCSP not-good, CT violation, an unreachable OCSP responder) must never be silently retried past the trust decision. **Fixed:** *DnsError / NetworkTlsError expose a correct terminal/transient signal; TlsTrustError stays terminal* — b.network.dns's DnsError was always-transient and b.network.tls's NetworkTlsError always-permanent, so err.permanent misled a consumer's retry loop in both directions — retrying a permanent DNS error (bad host / options / unsupported type / no-result) indefinitely, and never retrying a transient TLS network error (ECH connect failure, handshake timeout, DNS unavailable). err.permanent now reflects each error's actual transience by code and fails closed (unknown codes are permanent): DnsError is transient only for a failed network round-trip (dns/lookup-timeout, dns/resolve-failed, dns/reverse-failed, dns/doh-failed, dns/dot-failed, dns/dot-handshake-failed, dns/ddr-not-discovered, dns/system-failed), and permanent otherwise — including the caller-shape / environment errors raised before any network work (dns/transport-unavailable for an unconfigured transport, dns/dnr-no-resolvers for an empty/invalid resolver list, dns/setservers-failed for an invalid address, dns/no-system-resolvers when none are configured). NetworkTlsError is transient only for the network-layer ECH failures (tls/ech-connect-failed, tls/ech-timeout, tls/ech-dns-unavailable), permanent otherwise. TlsTrustError remains always-permanent so a trust-verification failure is never silently retried.
|
|
12
|
+
|
|
11
13
|
- v0.15.27 (2026-06-25) — **Internal test-harness reliability only — the published library is byte-for-byte identical to 0.15.26.** The legacy single-layer smoke files used fixed-duration setTimeout sleeps to wait for asynchronous conditions (a job processed, a lease TTL lapsing, an audit flush completing). On a contended CI runner a fixed sleep is both flake-prone (too short under load) and slow (it always burns the full budget). Those condition-waits are converted to the harness's polling helpers — waitUntil for observable predicates, passiveObserve for deliberate real-time elapses, withTestTimeout for hang guards — which exit early on fast platforms and give contended platforms the full budget. No shipped framework code changed; this release is byte-for-byte identical to 0.15.26 for operators. **Fixed:** *Smoke layer files poll for conditions instead of fixed setTimeout sleeps* — The single-layer smoke files waited on asynchronous conditions with fixed-duration setTimeout sleeps, which flake under SMOKE_PARALLEL load and always burn their full budget. The condition-waits are converted to the polling helpers (waitUntil / passiveObserve / withTestTimeout) so they exit early on fast platforms and stay robust on contended ones; non-wait timers (abort triggers, child/socket watchdogs, simulated-latency mocks) are unchanged. This is test-harness reliability only — no shipped framework behavior changed.
|
|
12
14
|
|
|
13
15
|
- v0.15.26 (2026-06-25) — **Internal test-harness correctness only — the published library is byte-for-byte identical to 0.15.25.** The smoke runner requires each test module and awaits its exported run(). Several tests were instead written as a top-level (async function () {...})() IIFE that runs detached at require-time, so the runner measured and reported the test's result before the IIFE's post-await assertions executed — those checks silently never ran (one parser test exercised 4 of its 26 assertions, and a failure after the first await would have gone unseen as a false pass). Those tests are converted to the exported-run form so the runner awaits their full assertion set, and a codebase-patterns detector now refuses a top-level async IIFE in a test file so the pattern cannot return. No shipped framework code changed; this release is byte-for-byte identical to 0.15.25 for operators. **Fixed:** *Detached-IIFE tests now run their full assertion set under the smoke runner* — A test written as a top-level (async function () {...})() IIFE runs detached when the runner requires it: the runner only awaits an exported run(), so it reported the file's result before the IIFE's awaited assertions executed, and every check after the first await silently did not count (one parsers test ran 4 of 26). Such tests are rewritten to define async function run(), export it, and invoke it under if (require.main === module), so the runner awaits the complete set; a detector refuses a re-introduced top-level async IIFE in a test file. This is test-harness correctness only — no shipped framework behavior changed.
|
package/lib/network-dns.js
CHANGED
|
@@ -16,7 +16,34 @@ var validateOpts = require("./validate-opts");
|
|
|
16
16
|
var { defineClass } = require("./framework-error");
|
|
17
17
|
var { boundedMap } = require("./bounded-map");
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// DnsError carries a terminal-vs-transient signal on err.permanent so a caller
|
|
20
|
+
// driving a retry loop knows which failures are worth re-attempting. Fail
|
|
21
|
+
// CLOSED: only a failure of an actual network round-trip that a retry can
|
|
22
|
+
// plausibly fix is transient (a lookup timeout, a resolve/reverse query that
|
|
23
|
+
// failed on the wire, a DoH/DoT exchange that failed, a DDR discovery query that
|
|
24
|
+
// found nothing). Everything else is permanent — bad config / options, an
|
|
25
|
+
// unsupported query, a malformed or empty/NXDOMAIN-style reply, AND the
|
|
26
|
+
// caller-shape / environment errors raised BEFORE any network work: requesting a
|
|
27
|
+
// transport that was never configured (dns/transport-unavailable), an
|
|
28
|
+
// empty/invalid designated-resolver list (dns/dnr-no-resolvers), an invalid
|
|
29
|
+
// setServers address (dns/setservers-failed), or no system resolvers configured
|
|
30
|
+
// (dns/no-system-resolvers). A retry cannot make invalid input or absent
|
|
31
|
+
// configuration valid.
|
|
32
|
+
var DNS_TRANSIENT_CODES = {
|
|
33
|
+
"dns/lookup-timeout": true,
|
|
34
|
+
"dns/system-failed": true,
|
|
35
|
+
"dns/resolve-failed": true,
|
|
36
|
+
"dns/reverse-failed": true,
|
|
37
|
+
"dns/doh-http": true,
|
|
38
|
+
"dns/doh-failed": true,
|
|
39
|
+
"dns/dot-handshake-failed": true,
|
|
40
|
+
"dns/dot-failed": true,
|
|
41
|
+
"dns/ddr-not-discovered": true,
|
|
42
|
+
};
|
|
43
|
+
function _dnsErrorIsPermanent(code) {
|
|
44
|
+
return !Object.prototype.hasOwnProperty.call(DNS_TRANSIENT_CODES, code);
|
|
45
|
+
}
|
|
46
|
+
var DnsError = defineClass("DnsError", { permanentClassifier: _dnsErrorIsPermanent });
|
|
20
47
|
|
|
21
48
|
// Protocol-fixed byte counts and radixes — passthrough through C.BYTES
|
|
22
49
|
// keeps every numeric literal routed through one helper.
|
package/lib/network-tls.js
CHANGED
|
@@ -16,8 +16,28 @@ var lazyRequire = require("./lazy-require");
|
|
|
16
16
|
var safeAsync = require("./safe-async");
|
|
17
17
|
var { defineClass } = require("./framework-error");
|
|
18
18
|
|
|
19
|
+
// TlsTrustError is a TRUST-verification failure (bad CA/PEM, fingerprint
|
|
20
|
+
// mismatch, OCSP not-good / revoked, CT violation, hostname/PKIX failure, an
|
|
21
|
+
// unreachable OCSP responder). These are ALWAYS permanent: a caller must never
|
|
22
|
+
// silently retry past a trust decision — a transient-looking OCSP-fetch failure
|
|
23
|
+
// is the operator's soft-fail policy to make explicitly, not something a retry
|
|
24
|
+
// loop should paper over. Fail closed.
|
|
19
25
|
var TlsTrustError = defineClass("TlsTrustError", { alwaysPermanent: true });
|
|
20
|
-
|
|
26
|
+
|
|
27
|
+
// NetworkTlsError carries a terminal-vs-transient signal on err.permanent. Fail
|
|
28
|
+
// CLOSED: only the network-layer ECH failures a retry can plausibly fix (a
|
|
29
|
+
// connect failure, a timeout, DNS being momentarily unavailable) are transient;
|
|
30
|
+
// bad options, a malformed ECH config, and PKIX hostname/SAN validation failures
|
|
31
|
+
// are permanent (config / validation errors a retry cannot fix).
|
|
32
|
+
var TLS_TRANSIENT_CODES = {
|
|
33
|
+
"tls/ech-connect-failed": true,
|
|
34
|
+
"tls/ech-timeout": true,
|
|
35
|
+
"tls/ech-dns-unavailable": true,
|
|
36
|
+
};
|
|
37
|
+
function _networkTlsErrorIsPermanent(code) {
|
|
38
|
+
return !Object.prototype.hasOwnProperty.call(TLS_TRANSIENT_CODES, code);
|
|
39
|
+
}
|
|
40
|
+
var NetworkTlsError = defineClass("NetworkTlsError", { permanentClassifier: _networkTlsErrorIsPermanent });
|
|
21
41
|
|
|
22
42
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
23
43
|
var audit = lazyRequire(function () { return require("./audit"); });
|
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:1bbafc03-fa7f-407c-b852-9caa724d07c2",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
8
|
+
"timestamp": "2026-06-26T06:34:07.791Z",
|
|
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.15.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.15.28",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.15.
|
|
25
|
+
"version": "0.15.28",
|
|
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.15.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.15.28",
|
|
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.15.
|
|
57
|
+
"ref": "@blamejs/core@0.15.28",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|