@blamejs/core 0.9.12 → 0.9.14
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/api-snapshot.js +4 -1
- package/lib/audit.js +29 -17
- package/lib/circuit-breaker.js +21 -6
- package/lib/crypto.js +145 -0
- package/lib/dsr.js +22 -15
- package/lib/inbox.js +21 -15
- package/lib/metrics.js +247 -0
- package/lib/middleware/idempotency-key.js +150 -0
- package/lib/pqc-agent.js +116 -26
- package/lib/retry.js +50 -0
- package/lib/self-update-standalone-verifier.js +280 -0
- package/lib/self-update.js +14 -8
- package/lib/vault/rotate.js +6 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.selfUpdate.standaloneVerifier
|
|
4
|
+
* @nav Production
|
|
5
|
+
* @title Self-Update Standalone Verifier
|
|
6
|
+
* @order 640
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* Zero-dep companion to `b.selfUpdate.verify` for install-pipeline
|
|
10
|
+
* contexts that run BEFORE the framework itself is installed —
|
|
11
|
+
* Dockerfile build stages, `install.sh`, `update.sh`, SEA-bundle
|
|
12
|
+
* verification at deploy time. The full `b.selfUpdate.verify`
|
|
13
|
+
* chain reaches into `b.crypto`, `b.httpClient`, `b.audit`, vendor
|
|
14
|
+
* imports, etc.; none of those exist yet when an operator's
|
|
15
|
+
* install script runs `node verify-release.js` against the
|
|
16
|
+
* downloaded artifact.
|
|
17
|
+
*
|
|
18
|
+
* This module is intentionally hermetic — `node:crypto` + `node:fs`
|
|
19
|
+
* only, no framework imports, no third-party modules. Operators
|
|
20
|
+
* physically copy the file into their install pipeline alongside a
|
|
21
|
+
* public-key module they own. Both go into version control on the
|
|
22
|
+
* operator's side; neither updates without their explicit action.
|
|
23
|
+
*
|
|
24
|
+
* Surface (single function):
|
|
25
|
+
*
|
|
26
|
+
* verify(assetPath, signaturePath, pubkeyPem, opts?) → {
|
|
27
|
+
* ok: boolean,
|
|
28
|
+
* sha3_512: string, // hex digest of asset bytes (SBOM correlation)
|
|
29
|
+
* sha256: string, // hex digest of asset bytes (defense-in-depth)
|
|
30
|
+
* alg: string, // detected algorithm: "ecdsa-p384" | "ed25519" | "ml-dsa-87"
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* The function refuses to load the asset into memory in one go;
|
|
34
|
+
* it streams the bytes through both hashers + the signature
|
|
35
|
+
* verifier so multi-GB SEA bundles don't OOM the install runner.
|
|
36
|
+
*
|
|
37
|
+
* Throws on:
|
|
38
|
+
* - missing asset / signature / pubkey file
|
|
39
|
+
* - unrecognized pubkey PEM shape
|
|
40
|
+
* - signature length mismatch with the algorithm
|
|
41
|
+
* - cryptographic verify failure
|
|
42
|
+
*
|
|
43
|
+
* Per the operator's request that surfaced this primitive
|
|
44
|
+
* (hermitstash-sync 2026-05-13): the install pipeline needs P-384
|
|
45
|
+
* ECDSA + SHA3-512 as the baseline cross-check. ML-DSA-87 is also
|
|
46
|
+
* supported when the operator's pubkey carries the corresponding
|
|
47
|
+
* OID (Node 22+ via the FIPS 204 OIDs in node:crypto).
|
|
48
|
+
*
|
|
49
|
+
* ## How operators consume this
|
|
50
|
+
*
|
|
51
|
+
* ```sh
|
|
52
|
+
* # one-time copy at framework-install time:
|
|
53
|
+
* cp "$(node -p "require('@blamejs/core').selfUpdate.standaloneVerifier.path")" \
|
|
54
|
+
* install/standalone-verifier.js
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* ```js
|
|
58
|
+
* // install/verify-release.js (operator-owned, in their repo):
|
|
59
|
+
* var verifier = require("./standalone-verifier");
|
|
60
|
+
* var pubkey = require("./release-pubkey"); // operator-owned PEM
|
|
61
|
+
*
|
|
62
|
+
* var result = verifier.verify(
|
|
63
|
+
* "/tmp/blamejs-sea-bundle",
|
|
64
|
+
* "/tmp/blamejs-sea-bundle.sig",
|
|
65
|
+
* pubkey,
|
|
66
|
+
* );
|
|
67
|
+
* if (!result.ok) {
|
|
68
|
+
* process.stderr.write("release verification FAILED\n");
|
|
69
|
+
* process.exit(1);
|
|
70
|
+
* }
|
|
71
|
+
* process.stdout.write("verified " + result.alg + " sha3-512=" + result.sha3_512 + "\n");
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* The module is also reachable as `b.selfUpdate.standaloneVerifier.verify`
|
|
75
|
+
* from inside a fully-installed framework process — useful for tests
|
|
76
|
+
* that exercise the same code path the operator's install pipeline
|
|
77
|
+
* does, without forking a subprocess.
|
|
78
|
+
*
|
|
79
|
+
* @card
|
|
80
|
+
* Zero-dep verifier for use BEFORE the framework is installed.
|
|
81
|
+
* Install-pipeline scripts copy this file alongside an operator-owned
|
|
82
|
+
* pubkey to verify signed release artifacts during Dockerfile build
|
|
83
|
+
* or systemd `install.sh`. node:crypto + node:fs only.
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
var nodeCrypto = require("crypto");
|
|
87
|
+
var nodeFs = require("fs");
|
|
88
|
+
|
|
89
|
+
// _streamHashAndVerify — read the asset in 64 KiB chunks, feed each
|
|
90
|
+
// chunk into sha256, sha3-512, AND the signature verifier in parallel.
|
|
91
|
+
// Single pass over the file; no in-memory copy. node:crypto's
|
|
92
|
+
// `createVerify` consumes streaming input via `.update()` for ECDSA +
|
|
93
|
+
// EdDSA; ML-DSA's `crypto.verify` requires the full payload, so we
|
|
94
|
+
// also accumulate to a buffer ONLY when the alg requires it.
|
|
95
|
+
function _detectAlg(pubkeyPem) {
|
|
96
|
+
// Inspect the PEM header / SPKI for a recognizable curve / OID. The
|
|
97
|
+
// pubkey PEM carries the algorithm identifier in the SPKI ASN.1; we
|
|
98
|
+
// load it via createPublicKey() and read `asymmetricKeyType` +
|
|
99
|
+
// `asymmetricKeyDetails.namedCurve`.
|
|
100
|
+
var key;
|
|
101
|
+
try {
|
|
102
|
+
key = nodeCrypto.createPublicKey(pubkeyPem);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
throw new Error("standalone-verifier: pubkey PEM did not parse: " +
|
|
105
|
+
(e && e.message ? e.message : String(e)));
|
|
106
|
+
}
|
|
107
|
+
var t = key.asymmetricKeyType;
|
|
108
|
+
if (t === "ec") {
|
|
109
|
+
var curve = key.asymmetricKeyDetails && key.asymmetricKeyDetails.namedCurve;
|
|
110
|
+
if (curve === "P-384" || curve === "secp384r1") return { alg: "ecdsa-p384", key: key };
|
|
111
|
+
throw new Error("standalone-verifier: unsupported EC curve '" + curve + "' (need P-384)");
|
|
112
|
+
}
|
|
113
|
+
if (t === "ed25519") return { alg: "ed25519", key: key };
|
|
114
|
+
if (t === "ml-dsa-87" || t === "ml-dsa") return { alg: "ml-dsa-87", key: key };
|
|
115
|
+
throw new Error("standalone-verifier: unrecognized pubkey type '" + t + "' " +
|
|
116
|
+
"(need ecdsa-p384, ed25519, or ml-dsa-87)");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @primitive b.selfUpdate.standaloneVerifier.verify
|
|
121
|
+
* @signature b.selfUpdate.standaloneVerifier.verify(assetPath, signaturePath, pubkeyPem)
|
|
122
|
+
* @since 0.9.13
|
|
123
|
+
* @status stable
|
|
124
|
+
* @related b.selfUpdate.verify
|
|
125
|
+
*
|
|
126
|
+
* Verify a signed release asset using only `node:crypto` + `node:fs`
|
|
127
|
+
* (no framework imports). For install-pipeline contexts where the
|
|
128
|
+
* framework itself is not yet installed.
|
|
129
|
+
*
|
|
130
|
+
* Streams the asset in 64 KiB chunks through SHA-256 + SHA-3-512 + the
|
|
131
|
+
* signature verifier in parallel — single allocation peak (one buffer
|
|
132
|
+
* sized to fstat(asset).size for Ed25519 / ML-DSA-87, ECDSA P-384 needs
|
|
133
|
+
* no buffer because createVerify is incremental).
|
|
134
|
+
*
|
|
135
|
+
* Returns `{ ok, sha3_512, sha256, alg }` on success; throws on
|
|
136
|
+
* unrecognized pubkey shape, missing files, or signature mismatch.
|
|
137
|
+
* `alg` is one of `"ecdsa-p384"`, `"ed25519"`, `"ml-dsa-87"` (auto-
|
|
138
|
+
* detected from the pubkey PEM).
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* var verifier = require("./standalone-verifier");
|
|
142
|
+
* var pubkey = require("./release-pubkey");
|
|
143
|
+
* var result = verifier.verify(
|
|
144
|
+
* "/tmp/blamejs-sea-bundle",
|
|
145
|
+
* "/tmp/blamejs-sea-bundle.sig",
|
|
146
|
+
* pubkey,
|
|
147
|
+
* );
|
|
148
|
+
* if (!result.ok) process.exit(1);
|
|
149
|
+
* process.stdout.write("verified " + result.alg + " sha3-512=" + result.sha3_512 + "\n");
|
|
150
|
+
*/
|
|
151
|
+
function verify(assetPath, signaturePath, pubkeyPem) {
|
|
152
|
+
if (typeof assetPath !== "string" || assetPath.length === 0) {
|
|
153
|
+
throw new Error("standalone-verifier.verify: assetPath must be a non-empty string");
|
|
154
|
+
}
|
|
155
|
+
if (typeof signaturePath !== "string" || signaturePath.length === 0) {
|
|
156
|
+
throw new Error("standalone-verifier.verify: signaturePath must be a non-empty string");
|
|
157
|
+
}
|
|
158
|
+
if (typeof pubkeyPem !== "string" || pubkeyPem.indexOf("-----BEGIN ") !== 0) {
|
|
159
|
+
throw new Error("standalone-verifier.verify: pubkeyPem must be a PEM-encoded public key string");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Open both files BEFORE parsing the pubkey so we own stable fds
|
|
163
|
+
// against TOCTOU races (CodeQL js/file-system-race) — checking
|
|
164
|
+
// existsSync before readFileSync leaves a swap window. Asset opens
|
|
165
|
+
// first so a missing-asset path surfaces before a missing-sig path.
|
|
166
|
+
var assetFd;
|
|
167
|
+
try {
|
|
168
|
+
assetFd = nodeFs.openSync(assetPath, "r");
|
|
169
|
+
} catch (e) {
|
|
170
|
+
throw new Error("standalone-verifier.verify: asset not found at " + assetPath +
|
|
171
|
+
" — " + (e && e.message ? e.message : String(e)));
|
|
172
|
+
}
|
|
173
|
+
var sigFd;
|
|
174
|
+
try {
|
|
175
|
+
sigFd = nodeFs.openSync(signaturePath, "r");
|
|
176
|
+
} catch (e) {
|
|
177
|
+
nodeFs.closeSync(assetFd);
|
|
178
|
+
throw new Error("standalone-verifier.verify: signature not found at " + signaturePath +
|
|
179
|
+
" — " + (e && e.message ? e.message : String(e)));
|
|
180
|
+
}
|
|
181
|
+
var signature;
|
|
182
|
+
try {
|
|
183
|
+
var sigStat = nodeFs.fstatSync(sigFd);
|
|
184
|
+
signature = Buffer.allocUnsafe(sigStat.size);
|
|
185
|
+
if (sigStat.size > 0) nodeFs.readSync(sigFd, signature, 0, sigStat.size, 0);
|
|
186
|
+
} finally {
|
|
187
|
+
nodeFs.closeSync(sigFd);
|
|
188
|
+
}
|
|
189
|
+
if (signature.length === 0) {
|
|
190
|
+
nodeFs.closeSync(assetFd);
|
|
191
|
+
throw new Error("standalone-verifier.verify: signature file is empty");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
var detected;
|
|
195
|
+
try {
|
|
196
|
+
detected = _detectAlg(pubkeyPem);
|
|
197
|
+
} catch (e) {
|
|
198
|
+
nodeFs.closeSync(assetFd);
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
var alg = detected.alg;
|
|
202
|
+
var key = detected.key;
|
|
203
|
+
|
|
204
|
+
// Stream the asset through both hashers. For ECDSA we stream through
|
|
205
|
+
// createVerify (incremental). For Ed25519 / ML-DSA we pre-allocate
|
|
206
|
+
// ONE buffer of stat.size and stream-fill it at increasing offsets —
|
|
207
|
+
// single allocation peak, not the 2× peak that Buffer.concat([...chunks])
|
|
208
|
+
// produces. 64 KiB chunks match the framework's hash-while-streaming
|
|
209
|
+
// convention elsewhere.
|
|
210
|
+
var sha256 = nodeCrypto.createHash("sha256");
|
|
211
|
+
var sha3 = nodeCrypto.createHash("sha3-512");
|
|
212
|
+
var verifier = (alg === "ecdsa-p384") ? nodeCrypto.createVerify("sha3-512") : null;
|
|
213
|
+
var fullBuf = null;
|
|
214
|
+
var fullOff = 0;
|
|
215
|
+
if (verifier === null) {
|
|
216
|
+
var assetStat = nodeFs.fstatSync(assetFd);
|
|
217
|
+
fullBuf = Buffer.allocUnsafe(assetStat.size);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
var chunk = Buffer.allocUnsafe(64 * 1024); // allow:raw-byte-literal — module is zero-dep by contract; cannot import C.BYTES
|
|
222
|
+
while (true) {
|
|
223
|
+
var n = nodeFs.readSync(assetFd, chunk, 0, chunk.length, null);
|
|
224
|
+
if (n === 0) break;
|
|
225
|
+
var slice = chunk.subarray(0, n);
|
|
226
|
+
sha256.update(slice);
|
|
227
|
+
sha3.update(slice);
|
|
228
|
+
if (verifier) verifier.update(slice);
|
|
229
|
+
if (fullBuf) {
|
|
230
|
+
slice.copy(fullBuf, fullOff);
|
|
231
|
+
fullOff += n;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} finally {
|
|
235
|
+
nodeFs.closeSync(assetFd);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
var sha256Hex = sha256.digest("hex");
|
|
239
|
+
var sha3Hex = sha3.digest("hex");
|
|
240
|
+
|
|
241
|
+
var ok = false;
|
|
242
|
+
if (alg === "ecdsa-p384") {
|
|
243
|
+
// P-384 IEEE-P1363 sigs are exactly 96 bytes (48-byte r || 48-byte s).
|
|
244
|
+
// P-384 DER sigs are variable (~100-104 bytes — ASN.1 SEQUENCE
|
|
245
|
+
// wrapping two INTEGERs). Detect by length so we only call
|
|
246
|
+
// verifier.verify ONCE — calling it a second time after a failed
|
|
247
|
+
// verify returns stale state and silently passes tampered assets.
|
|
248
|
+
// 96 = P-384 IEEE-P1363 signature length; protocol constant, not a byte-size.
|
|
249
|
+
var dsaEncoding = signature.length === 96 ? "ieee-p1363" : "der"; // allow:raw-byte-literal — IEEE-P1363 P-384 signature length
|
|
250
|
+
ok = verifier.verify({ key: key, dsaEncoding: dsaEncoding }, signature);
|
|
251
|
+
} else if (alg === "ed25519") {
|
|
252
|
+
// fullBuf may be shorter than allocated (sparse files / size-races);
|
|
253
|
+
// slice to fullOff so verify sees only the bytes we actually read.
|
|
254
|
+
ok = nodeCrypto.verify(null, fullBuf.subarray(0, fullOff), key, signature);
|
|
255
|
+
} else if (alg === "ml-dsa-87") {
|
|
256
|
+
ok = nodeCrypto.verify(null, fullBuf.subarray(0, fullOff), key, signature);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!ok) {
|
|
260
|
+
throw new Error("standalone-verifier.verify: " + alg + " signature INVALID for " +
|
|
261
|
+
assetPath + " (sha3-512=" + sha3Hex.slice(0, 16) + "...). " + // allow:raw-byte-literal — 16-char hex prefix for forensic display, not bytes
|
|
262
|
+
"Either the asset was tampered with after signing, the signature " +
|
|
263
|
+
"doesn't match this asset, or the pubkey doesn't match the signing key.");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
ok: true,
|
|
268
|
+
sha3_512: sha3Hex,
|
|
269
|
+
sha256: sha256Hex,
|
|
270
|
+
alg: alg,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = {
|
|
275
|
+
verify: verify,
|
|
276
|
+
// Absolute path to this module file. Operators copy it via:
|
|
277
|
+
// cp "$(node -p "require('@blamejs/core').selfUpdate.standaloneVerifier.path")" \
|
|
278
|
+
// install/standalone-verifier.js
|
|
279
|
+
path: __filename,
|
|
280
|
+
};
|
package/lib/self-update.js
CHANGED
|
@@ -59,6 +59,7 @@ var safeJson = require("./safe-json");
|
|
|
59
59
|
var { URL: NodeUrl } = require("url");
|
|
60
60
|
var lazyRequire = require("./lazy-require");
|
|
61
61
|
var C = require("./constants");
|
|
62
|
+
var standaloneVerifier = require("./self-update-standalone-verifier");
|
|
62
63
|
var { boot } = require("./log");
|
|
63
64
|
var { defineClass } = require("./framework-error");
|
|
64
65
|
|
|
@@ -635,13 +636,18 @@ async function rollback(opts) {
|
|
|
635
636
|
}
|
|
636
637
|
|
|
637
638
|
module.exports = {
|
|
638
|
-
poll:
|
|
639
|
-
verify:
|
|
640
|
-
swap:
|
|
641
|
-
rollback:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
639
|
+
poll: poll,
|
|
640
|
+
verify: verify,
|
|
641
|
+
swap: swap,
|
|
642
|
+
rollback: rollback,
|
|
643
|
+
// Standalone verifier — zero-dep companion for install-pipeline
|
|
644
|
+
// contexts that run BEFORE the framework is installed (Dockerfile
|
|
645
|
+
// build stages, install.sh, update.sh). See the module's intro for
|
|
646
|
+
// the copy-this-file workflow.
|
|
647
|
+
standaloneVerifier: standaloneVerifier,
|
|
648
|
+
SelfUpdateError: SelfUpdateError,
|
|
649
|
+
ALLOWED_HASH_ALGS: ALLOWED_HASH_ALGS,
|
|
650
|
+
DEFAULT_HASH_ALG: DEFAULT_HASH_ALG,
|
|
645
651
|
// Internal — exposed for the layer-0 test suite only.
|
|
646
|
-
_compareTags:
|
|
652
|
+
_compareTags: _compareTags,
|
|
647
653
|
};
|
package/lib/vault/rotate.js
CHANGED
|
@@ -52,6 +52,7 @@ var fs = require("fs");
|
|
|
52
52
|
var path = require("path");
|
|
53
53
|
var { DatabaseSync } = require("node:sqlite");
|
|
54
54
|
var atomicFile = require("../atomic-file");
|
|
55
|
+
var safeSql = require("../safe-sql");
|
|
55
56
|
var C = require("../constants");
|
|
56
57
|
var cryptoField = require("../crypto-field");
|
|
57
58
|
var cryptoLib = require("../crypto");
|
|
@@ -437,8 +438,11 @@ function _walkAndReSeal(node, oldKeys, newKeys) {
|
|
|
437
438
|
function _runStmt(db, sql) { db.prepare(sql).run(); }
|
|
438
439
|
|
|
439
440
|
function _rotateColumn(db, table, column, oldKeys, newKeys, batchSize, progress) {
|
|
440
|
-
|
|
441
|
-
|
|
441
|
+
// Identifiers reach SQL through safeSql.quoteIdentifier — runs
|
|
442
|
+
// validateIdentifier (rejects bad shape / reserved words /
|
|
443
|
+
// sqlite_-prefix) + emits the dialect-correct quoted form.
|
|
444
|
+
var qt = safeSql.quoteIdentifier(table, "sqlite");
|
|
445
|
+
var qc = safeSql.quoteIdentifier(column, "sqlite");
|
|
442
446
|
var total = db.prepare("SELECT COUNT(*) AS n FROM " + qt + " WHERE " + qc + " IS NOT NULL").get().n;
|
|
443
447
|
if (total === 0) return 0;
|
|
444
448
|
|
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.6",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:cc380387-6002-4e34-863f-7bb3090533eb",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-13T20:27:26.640Z",
|
|
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.9.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.9.14",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.9.
|
|
25
|
+
"version": "0.9.14",
|
|
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.9.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.9.14",
|
|
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.9.
|
|
57
|
+
"ref": "@blamejs/core@0.9.14",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|