@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 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.
@@ -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
- var DnsError = defineClass("DnsError", { alwaysPermanent: false });
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.
@@ -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
- var NetworkTlsError = defineClass("NetworkTlsError", { alwaysPermanent: true });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.15.27",
3
+ "version": "0.15.28",
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.5",
5
- "serialNumber": "urn:uuid:2b373536-53d6-476a-9d21-69b153ba2a17",
5
+ "serialNumber": "urn:uuid:1bbafc03-fa7f-407c-b852-9caa724d07c2",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-06-26T02:24:20.400Z",
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.27",
22
+ "bom-ref": "@blamejs/core@0.15.28",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.15.27",
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.27",
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.27",
57
+ "ref": "@blamejs/core@0.15.28",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]