@blamejs/core 0.18.48 → 0.18.49

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,14 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.18.x
10
10
 
11
+ - v0.18.49 (2026-08-22) — **Comments in the shipped tree that answered a question you cannot ask.** A comment in `lib/` reaches anyone who opens the published package, and twenty of them were written for a reader who remembers the previous version or can look up an issue number that resolves nowhere outside this repository. They now say what the code does and why, in the present tense, keeping every reason they carried. No behaviour changes. **Changed:** *Comments that narrated a change now describe the code* — Fourteen comments across `lib/` were written against a version the reader has never seen: "this used to be opt-in", "it used to declare them in every profile anyway", "the legacy fallback was removed". Each is now a property of the design rather than a diff, and each keeps the reason it carried. The zero-width scan's comment still explains that an opt-in argument is an argument a caller can omit, and still names the six call sites that omitted it — it just no longer reads as a changelog entry pasted into the source.
12
+
13
+ One of them was wrong in a way worth naming: a note in the gate-contract context builder said a guard "serves bytes it used to examine", which reads as a past version's behaviour. It means bytes the guard would otherwise examine, and now says so. · *Issue numbers that resolve nowhere are gone; the upstream ones stay* — Six comments cited a bare `issue #100` / `#330` / `#430` / `#532`. Opened from the published tarball those name nothing an operator can look up, and the explanation beside them was already doing the work. The references are gone and the explanations stay.
14
+
15
+ The references that name their upstream are untouched and now read unambiguously: `cure53/DOMPurify issue #233` on the SVG `xlink:href` filtering, and CycloneDX spec issue #702 on the EU CRA property mapping, which is now attributed on both of its mentions rather than one.
16
+
17
+ The boundary is what ships. `test/` carries 144 of the same kind of reference, most of them a `PR #nnn` on a regression test, and those are left alone: they resolve in the repository a contributor is already reading, and on a regression test the pull-request reference is often the most useful line in the comment. · *Three sentences in SECURITY.md* — A bot-guard rationale said an abuser "simply sends the header", which tells a reader the step is trivial and adds nothing to the argument. Two lists ended in `etc.` without saying where the rest was: the post-quantum algorithm-agility example now names HQC and FrodoKEM as the examples they are, and the session-risk scorer now says it scores whatever signal you have. The other six `etc.` in the file name their generating rule in the next clause and are unchanged.
18
+
11
19
  - v0.18.48 (2026-08-22) — **A guard option you set and a guard option that exists were not the same set.** Every `*Policy` option across the guard family now names the values it takes, and refuses everything else where options are resolved rather than at the first request that trips the scan. Holding the options to their vocabularies surfaced four places where what a guard advertised and what it did had come apart: an option documented under a name nothing read, a value that disabled a critical check without appearing in any opts block, two policies whose settings the gate's floor overrode anyway, and one declared in all three profiles and read nowhere. **Fixed:** *A guard policy refuses a value outside its vocabulary* — The two hundred and sixty-two `*Policy` options across the guard family each declare the values they accept, checked where options are resolved — boot, not the first hostile input — and on every entry point, including the `gate()` and `validate()` of guards that bind their own resolver.
12
20
 
13
21
  The failure it ends is quiet, because policies are read leniently. `duplicateKeyPolicy: "rejct"` is not `allow`, so the check still runs; it is not `reject` either, so the finding drops from critical to a warning. The operator asked to refuse a duplicate key and silently got an audit line. Nothing was let through that a stricter reading would have caught, so this was never a hole — but a setting that means something other than what it says is worth a restart to learn about.
@@ -178,8 +178,8 @@ function build(opts) {
178
178
  }
179
179
  }
180
180
 
181
- // Hyperparameters → CycloneDX properties[] kv pairs per spec
182
- // issue #702 EU CRA alignment.
181
+ // Hyperparameters → CycloneDX properties[] kv pairs, per CycloneDX spec
182
+ // issue #702 (EU CRA alignment).
183
183
  var properties = [];
184
184
  if (opts.hyperparameters && typeof opts.hyperparameters === "object") {
185
185
  var keys = Object.keys(opts.hyperparameters);
package/lib/audit-sign.js CHANGED
@@ -85,8 +85,8 @@ var _err = AuditSignError.factory;
85
85
  // via opts.algorithm — e.g. `auditSigning: { algorithm: "ml-dsa-87" }`
86
86
  // for throughput-sensitive deployments. Every key file MUST carry the
87
87
  // `algorithm` field on disk — the framework refuses to load a key file
88
- // that lacks it. The legacy implicit-default-to-ml-dsa-87 fallback was
89
- // removed as part of the pre-v1 compat-shim sweep.
88
+ // that lacks it. There is no implicit default: a key file whose algorithm has
89
+ // to be guessed is a key file whose signatures cannot be attributed.
90
90
  var DEFAULT_SIGNING_ALG = "slh-dsa-shake-256f";
91
91
  // ml-dsa-65 (FIPS 204 Category 3, ~192-bit symmetric security) is opt-
92
92
  // in alongside ml-dsa-87 — same code path (both auto-detected by
@@ -102,7 +102,7 @@ var SIGNING_KEY_SCHEMA = {
102
102
  properties: {
103
103
  publicKey: { type: "string" },
104
104
  privateKey: { type: "string" },
105
- algorithm: { type: "string" }, // load-time-required — _initPlaintext + _initWrapped both throw KEY_FILE_MISSING_ALG / UNWRAPPED_MISSING_ALG when the field is absent (legacy implicit-default-to-ml-dsa-87 was removed in the pre-v1 compat-shim sweep). Schema's `required` keeps publicKey + privateKey only so the runtime checks fire with the precise error codes operators have wired alerting on.
105
+ algorithm: { type: "string" }, // load-time-required — _initPlaintext + _initWrapped both throw KEY_FILE_MISSING_ALG / UNWRAPPED_MISSING_ALG when the field is absent, rather than defaulting to an algorithm the file never named. Schema's `required` keeps publicKey + privateKey only so the runtime checks fire with the precise error codes operators have wired alerting on.
106
106
  },
107
107
  };
108
108
 
@@ -986,7 +986,7 @@ function _requireCredentialIdMatches(response, authoritativeId, why) {
986
986
  // _credentialDescriptors deliberately supports -- while the browser returns
987
987
  // the unpadded spelling. Two spellings of ONE credential must not read as
988
988
  // two credentials, or the compatibility this binding sits next to would
989
- // lock out exactly the deployments it was added for.
989
+ // lock out exactly the deployments it exists for.
990
990
  var expected = _canonicalBase64Url(authoritativeId);
991
991
  var fields = ["id", "rawId"];
992
992
  var stated = 0;
@@ -1697,8 +1697,8 @@ function bundleAdapterStorage(opts) {
1697
1697
  // "unknown" rather than risk a full payload load. For
1698
1698
  // rewrap, we already have to load the payload (to unwrap),
1699
1699
  // so fall back to a sniffEnvelope on the loaded sealed
1700
- // bytes fixes the regression where adapters satisfying
1701
- // the minimum contract couldn't use rewrapBundle.
1700
+ // bytes, which keeps rewrapBundle reachable for an adapter
1701
+ // that satisfies only the minimum contract.
1702
1702
  if (envelopeKind === "unknown") {
1703
1703
  envelopeKind = archiveLazy().sniffEnvelope(sealed);
1704
1704
  }
@@ -136,8 +136,7 @@ function _parseSfBrandList(s) {
136
136
  // sf-list members don't allow parenthesized inner-list values in
137
137
  // the Sec-CH-UA grammar (only sf-string + parameters), so the
138
138
  // simple top-level comma split suffices — no `depth` tracking
139
- // needed (the earlier inline shape carried defensive paren
140
- // tracking left over from a generic sf-list walker prototype).
139
+ // needed.
141
140
  var pieces = structuredFields.splitTopLevel(t, ",");
142
141
  var out = [];
143
142
  for (var i = 0; i < pieces.length; i += 1) {
@@ -860,11 +860,12 @@ function detectCharThreats(text, opts, codePrefix) {
860
860
  // zero-width-only input under `strip` reaches the sanitizer instead of being
861
861
  // served unchanged.
862
862
  //
863
- // This used to be OPT-IN via a `zeroWidthSeverity` argument, which meant a
864
- // caller that forgot it disabled the scan no matter what the operator's
865
- // policy said and six did, including the shared gate path, so seven guards
866
- // declared `zeroWidthPolicy: "reject"` and never applied it. Six more passed
867
- // a hardcoded "warn", which dispositions to serve, so they reported the
863
+ // Not opt-in, and deliberately: an argument the caller has to pass in order
864
+ // for the scan to run is an argument a caller can omit, and omitting it
865
+ // disables the scan no matter what the operator's policy says. Six call
866
+ // sites omitted it, the shared gate path among them, so seven guards declared
867
+ // `zeroWidthPolicy: "reject"` and never applied it. Six more passed a
868
+ // hardcoded "warn", which dispositions to serve, so they reported the
868
869
  // character and shipped it anyway.
869
870
  //
870
871
  // Severity follows the resolved POLICY, not a per-caller constant — the same
@@ -305,7 +305,7 @@ function validateGateShape(gate, label, errorClass) {
305
305
  // - `Object.assign({}, base, ...)` copies OWN enumerable properties, so a
306
306
  // class instance whose fields come from prototype getters arrives with them
307
307
  // missing. A guard reading an absent subject as nothing-to-inspect then
308
- // serves bytes it used to examine.
308
+ // serves bytes it would otherwise examine.
309
309
  // - `Object.create(base)` fixes that but makes inherited getters run with the
310
310
  // DERIVED object as `this`, so a getter returning a private field throws
311
311
  // and a valid context is refused.
@@ -115,11 +115,12 @@ var MAGIC_SIGNATURES = Object.freeze([
115
115
 
116
116
  // Character-class policy for an entry NAME comes from `filenameProfile`, which
117
117
  // routes the name through b.guardFilename — this guard never reads a
118
- // bidi/control/null/zero-width policy of its own. It used to declare them in
119
- // every profile anyway, so an operator passing `zeroWidthPolicy` here was
120
- // configuring nothing and had no way to find that out. The declarations are
121
- // gone rather than wired, because `filenameProfile` is already the one place
122
- // that decides it and two spellings of the same setting is how they disagree.
118
+ // bidi/control/null/zero-width policy of its own, and does not declare them in
119
+ // its profiles either. Declaring an option nothing reads leaves an operator
120
+ // setting `zeroWidthPolicy` here configuring nothing, with no way to find that
121
+ // out. Wiring them instead would give the same setting two spellings, which is
122
+ // how the two come to disagree, and `filenameProfile` is already the one place
123
+ // that decides it.
123
124
  var PROFILES = Object.freeze({
124
125
  "strict": {
125
126
  traversalPolicy: "reject",
@@ -201,14 +201,15 @@ function _classUsesSetSyntax(text, from) {
201
201
  // ---- pattern parsing ------------------------------------------------------
202
202
  //
203
203
  // Every analysis below reads a parse tree. None of them reads the pattern
204
- // source. Reading source with regexes is what this module used to do, and each
205
- // reader drew the token boundaries a little differently: one could not see
206
- // past a nested group, one decided whether a `?` was a quantifier by looking at
207
- // the previous CHARACTER (so the `?` in `\*?` read as a lazy marker and the
208
- // length variation it contributes was lost), one capped the digits inside
209
- // `{n,m}` (so a longer bound read as no quantifier at all). Each disagreement
210
- // was a way to write a catastrophic pattern that one reader found and another
211
- // waved through, and patching them one at a time only moved the edge.
204
+ // source. Reading source with regexes puts a separate reader behind each
205
+ // analysis, and separate readers draw the token boundaries differently: one
206
+ // cannot see past a nested group, one decides whether a `?` is a quantifier by
207
+ // looking at the previous CHARACTER (so the `?` in `\*?` reads as a lazy marker
208
+ // and the length variation it contributes is lost), one caps the digits inside
209
+ // `{n,m}` (so a longer bound reads as no quantifier at all). Every such
210
+ // disagreement is a way to write a catastrophic pattern that one reader finds
211
+ // and another waves through, and patching them one at a time moves the edge
212
+ // rather than closing it.
212
213
  //
213
214
  // So: one tokenizer, one tree, and anything it cannot represent becomes an
214
215
  // OPAQUE node — which every analysis treats as "cannot prove", never as
@@ -754,9 +755,9 @@ function _codePointAt(src, at, flags) {
754
755
  // `k`, but `k` uppercases to `K` and never back to the Kelvin sign, so a pass
755
756
  // starting at `K` never reaches it and two branches that both match it were
756
757
  // proven disjoint. Which characters an engine treats as equal under `i` is a
757
- // rule the language states, so the rule is applied it used to be discovered
758
- // by building a RegExp per pair of characters and seeing which ones matched,
759
- // which is the screen reaching for the construct it exists to screen.
758
+ // rule the language states, so the rule is applied rather than discovered by
759
+ // building a RegExp per pair of characters and seeing which ones match, which
760
+ // would be the screen reaching for the construct it exists to screen.
760
761
  //
761
762
  // Only characters PRESENT in the pattern can create an overlap between two of
762
763
  // its sets, so the comparison is made over that alphabet alone. Pairs whose
@@ -796,9 +797,9 @@ function _foldGroups(src, flags) {
796
797
  var x = alphabet[a], y = alphabet[b];
797
798
  if (_linkedByCase(x, y)) continue; // already found by folding
798
799
  // Which characters an engine treats as the same under `i` is a rule, not
799
- // something to be discovered by asking. This used to build a RegExp per
800
- // pair and see whether one matched the other the screen reaching for
801
- // the very construct it screens, and a pattern's worth of them per call.
800
+ // something to be discovered by asking. Building a RegExp per pair and
801
+ // seeing whether one matches the other is the screen reaching for the very
802
+ // construct it screens, and a pattern's worth of them per call.
802
803
  // The rule itself is exact and costs a comparison.
803
804
  if (_canonical(x, unicodeMode) !== _canonical(y, unicodeMode)) continue;
804
805
  _linkFold(groups, x, y);
package/lib/mail-auth.js CHANGED
@@ -1227,12 +1227,9 @@ function _dmarcAuthorDomainLabels(domain) {
1227
1227
  if (!d) return null;
1228
1228
  // Both RFC 1035 §2.3.4 bounds — 63 octets per label and 253 for the whole
1229
1229
  // name — are enforced by `canonicalDomain` itself, so an over-long or empty
1230
- // label has already returned "" above. This function used to re-check the
1231
- // label bound because canonicalDomain enforced only the total; the comment
1232
- // here argued a label cap was a DNS wire rule rather than a naming one, which
1233
- // did not survive the observation that the 253 cap is equally a wire rule and
1234
- // was enforced there anyway. Fixing it at the definition means every caller
1235
- // gets it, not just this one.
1230
+ // label has already returned "" above. No re-check here: both are wire rules
1231
+ // of the same kind, and enforcing them where the name is defined gives them
1232
+ // to every caller rather than to whichever ones remembered.
1236
1233
  return d.split(".");
1237
1234
  }
1238
1235
 
package/lib/metrics.js CHANGED
@@ -1024,7 +1024,7 @@ function _serializeRegistry(registry) {
1024
1024
  // a JSON-friendly structured shape. Histograms get full buckets +
1025
1025
  // bucket counts so downstream consumers compose
1026
1026
  // `histogram_quantile()` against the snapshot without a separate
1027
- // exposition endpoint (issue #100).
1027
+ // exposition endpoint.
1028
1028
  var out = {};
1029
1029
  var names = registry.metrics instanceof Map
1030
1030
  ? Array.from(registry.metrics.keys()).sort()
@@ -1073,8 +1073,8 @@ function snapshotStartWriter(opts) {
1073
1073
  throw new MetricsError("metrics-snapshot/bad-fields",
1074
1074
  "metrics.snapshot.startWriter: opts.fields must be a function returning the snapshot object");
1075
1075
  }
1076
- // Issue #100 — optional `registry` handle pulls every registered
1077
- // metric into a structured `metrics` field in the JSON snapshot:
1076
+ // The optional `registry` handle pulls every registered metric into a
1077
+ // structured `metrics` field in the JSON snapshot:
1078
1078
  // counters / gauges as `{ value }` per label set, histograms as
1079
1079
  // `{ buckets, observations }` with bucket counts + sum + count.
1080
1080
  // Sidecar readers compose `histogram_quantile()` against the
@@ -1420,7 +1420,7 @@ function snapshotRender(snap, opts) {
1420
1420
  "metrics.snapshot.render: snap must be a startWriter-produced object (got " + typeof snap + ")");
1421
1421
  }
1422
1422
  var fields = snap.fields;
1423
- // Labeled registry families (issue #430) — a snapshot written with
1423
+ // Labeled registry families — a snapshot written with
1424
1424
  // startWriter's `registry` option carries every registered counter /
1425
1425
  // gauge / histogram under `metrics`. Both formats render them so a
1426
1426
  // sidecar consuming a snapshot written by another process gets the
package/lib/mtls-ca.js CHANGED
@@ -25,9 +25,9 @@
25
25
  * `ca.crl` (signed CRL derived from the registry).
26
26
  *
27
27
  * `caKeySealedMode` defaults to "required" — sealed file required,
28
- * plaintext refused. The legacy "auto" fallback was removed; it
29
- * defaulted to writing plaintext on a fresh install, which is the
30
- * inverse of the framework's security-defaults-on posture for
28
+ * plaintext refused. There is no "auto" mode, because deciding
29
+ * for the operator means writing plaintext on a fresh install,
30
+ * which is the inverse of the framework's security-defaults-on posture for
31
31
  * at-rest key material. The "disabled" mode is a dev-only opt-out
32
32
  * (operator must justify with audited reason).
33
33
  *
@@ -2554,7 +2554,7 @@ function create(opts) {
2554
2554
  path: paths.crl };
2555
2555
  }
2556
2556
 
2557
- // ---- Algorithm migration (issue #532) ----
2557
+ // ---- Algorithm migration ----
2558
2558
 
2559
2559
  // Serialize rotations on this handle. Two concurrent rotate() calls must not
2560
2560
  // both read the same current generation, both mint the next one, and clobber
@@ -535,8 +535,8 @@ function _dnsQueryLabels(host, primitive) {
535
535
  // A delimiter is the one that bites, because `domainToASCII` TRUNCATES at
536
536
  // one, so `example.com/evil` can masquerade as a trusted prefix of itself.
537
537
  //
538
- // Mirroring the rule was tried first, and the list of near-misses above is
539
- // what that produced. Asking the owner is the version that cannot drift.
538
+ // Mirroring the rule locally produces the list of near-misses above. Asking
539
+ // the owner is the version that cannot drift.
540
540
  var canonical = publicSuffix.canonicalDomain(h);
541
541
  if (!canonical) {
542
542
  throw new DnsError("dns/bad-host",
@@ -301,8 +301,8 @@ function create(opts) {
301
301
  // A no-store instance is still useful: the stateless fingerprint() reads no
302
302
  // store and is the soft device-binding building block for self-validating
303
303
  // tokens (a sealed cookie / JWT carrying the fingerprint inside). Rather than
304
- // refuse to construct (issue #330 fingerprint() unreachable without a
305
- // store), build the instance and let the persisted bind()/verify() lifecycle
304
+ // refuse to construct, which would put fingerprint() out of reach for want of
305
+ // a store, build the instance and let the persisted bind()/verify() lifecycle
306
306
  // throw a clear "no store configured" when actually called. Operators wanting
307
307
  // ONLY the stateless digest can also use the static
308
308
  // b.sessionDeviceBinding.fingerprint(req, opts) with no create() at all.
@@ -472,8 +472,8 @@ async function initFirstRunWrapped() {
472
472
  "failed to wrap new vault key: " + e.message);
473
473
  }
474
474
 
475
- // Atomic write via the framework's atomic-file primitive (temp + fsync +
476
- // rename + dir fsync — same flow this code used to inline manually).
475
+ // Atomic write via the framework's atomic-file primitive: temp + fsync +
476
+ // rename + dir fsync.
477
477
  atomicFile.writeSync(paths.sealed, sealed, { fileMode: 0o600 });
478
478
 
479
479
  log("generated and sealed new vault keypair (ML-KEM-1024 + P-384 hybrid)");
package/lib/ws-client.js CHANGED
@@ -453,8 +453,8 @@ class WsClient extends EventEmitter {
453
453
  if (lookup) tlsOpts.lookup = lookup;
454
454
  // The group preference arrives with the shared posture above, already
455
455
  // reflecting a runtime setKeyShares(). It lands as `ecdhCurve`: node:tls
456
- // has no `curves` option — it accepts that key and ignores it, which is
457
- // how this preference used to be dropped from the handshake in silence,
456
+ // has no `curves` option — it accepts that key and ignores it, so a
457
+ // preference sent under that name leaves the handshake in silence,
458
458
  // whereas a bad `ecdhCurve` throws. An operator value in dialTlsOpts
459
459
  // still wins, since it is merged last.
460
460
  socket = tls.connect(tlsOpts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.18.48",
3
+ "version": "0.18.49",
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:a63c2af3-b9b9-4c06-b74a-dd7049ac3f6c",
5
+ "serialNumber": "urn:uuid:a6344adf-71aa-4268-ba0f-0073a6ead16a",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-22T16:44:54.517Z",
8
+ "timestamp": "2026-08-22T18:41:08.842Z",
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.48",
22
+ "bom-ref": "@blamejs/core@0.18.49",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.18.48",
25
+ "version": "0.18.49",
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.48",
29
+ "purl": "pkg:npm/%40blamejs/core@0.18.49",
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.48",
57
+ "ref": "@blamejs/core@0.18.49",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]