@blamejs/core 0.12.12 → 0.12.13
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/backup/index.js +188 -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.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.13 (2026-05-23) — **`b.backup.bundleAdapterStorage.objectStoreAdapter` — wraps any `b.objectStore` backend (local / SigV4 / GCS / Azure-Blob) into the backup-adapter contract; closes the v0.11.2 "any custom backend" promise.** `b.backup.bundleAdapterStorage.objectStoreAdapter(client, opts)` adapts a `b.objectStore`-shaped client into the `{ writeFile, readFile, listKeys, deleteKey, hasKey }` adapter contract that `bundleAdapterStorage` consumes. Operators wire any of the four shipped object-store backends (`protocol: "local"` / `"sigv4"` for S3+MinIO / `"gcs"` / `"azure-blob"`) through the same recipient / passphrase wrap layers shipped in v0.12.10 and v0.12.11 — the bundle bytes hit the object-store `put` as an opaque envelope. `opts.prefix` namespaces every key under a fixed root inside the bucket so operators sharing a bucket across deployments keep listings scoped. Closes the deferral surfaced in v0.11.2 JSDoc and the v0.12.10 release-notes follow-up list: "S3 or any custom backend" is now wired with no operator-supplied adapter glue. **Added:** *`b.backup.bundleAdapterStorage.objectStoreAdapter(client, opts?)` — object-store-backed bundle storage* — Adapts any `b.objectStore.buildBackend({ protocol })` client (local / sigv4 / gcs / azure-blob) into the `{ writeFile, readFile, listKeys, deleteKey, hasKey }` adapter contract. NOT_FOUND errors from the underlying client translate to `backup/no-key` for the readFile path and to idempotent return-without-throw for deleteKey (matching the fsAdapter contract). hasKey routes through `client.head(key)` — NOT_FOUND → false; any other error propagates so operators can distinguish network failure from missing-key. Composes transparently with v0.12.10 `cryptoStrategy: "recipient"` and v0.12.11 `cryptoStrategy: "passphrase"` — the wrap envelope is the bytes hitting the object-store put. · *`opts.prefix` — per-deployment key namespacing inside the bucket* — Operators sharing one bucket across multiple deployments (per-environment / per-tenant / per-region) pass distinct prefixes so listings stay scoped. The prefix gets a trailing slash inserted automatically; traversal segments (`..`) and NUL bytes refused upfront with `backup/bad-arg` so a misconfigured prefix can't escape the operator's intended scope. listKeys strips the prefix on return so the adapter surface looks identical to the fsAdapter — operators switching backends don't see key-shape drift. **Security:** *Key path validation — traversal + NUL byte refusal at every adapter call* — Every `_scopedKey(key)` invocation refuses keys containing `..` traversal segments or NUL bytes upfront with `backup/bad-key` so a misconfigured bundleId or an attacker-controlled value never reaches the underlying `client.put(...)` / `client.get(...)`. Matches the same defensive posture the fsAdapter carries against operator-supplied key shapes.
|
|
12
|
+
|
|
11
13
|
- v0.12.12 (2026-05-23) — **`b.ai.disclosure.chatbot` + `b.ai.disclosure.deepfake` + `b.ai.disclosure.emotion` — EU AI Act Art. 50 transparency obligations (calendar-locked 2026-08-02) with US-CA AB-853 + China CAC GenAI cross-walk.** EU AI Act Art. 50 transparency primitives land ahead of the 2026-08-02 enforcement deadline. `b.ai.disclosure.chatbot(session, opts)` emits the Art. 50(1) first-contact "you are interacting with an AI system" disclosure with placement control (`first-message` / `always` / `on-request`). `b.ai.disclosure.deepfake(content, { contentType, placement, jurisdiction })` emits the Art. 50(4) synthetic-content label + machine-readable metadata payload for image / audio / video / text. `b.ai.disclosure.emotion({ systemType })` emits the Art. 50(3) emotion-recognition / biometric-categorisation notice. Each primitive emits a tamper-evident `ai-act/*-disclosure-applied` audit event so the compliance trail backs the user-facing notice. Cross-jurisdiction cross-walk lives in `opts.jurisdiction`: `"eu"` (default), `"us-ca"` adds AB-853 §22949.91 to the cross-walk array, `"cn"` adds CAC GenAI Measures Art. 12. The deepfake primitive returns a `schema: "c2pa-v1.4-ready"` metadata field that the v0.12.21 `b.contentCredentials` C2PA adapter will consume when it lands — this patch ships the label markup + schema; the C2PA manifest emission is the next composition. **Added:** *`b.ai.disclosure.chatbot(session, opts)` — Art. 50(1) first-contact disclosure* — Operators interacting with natural persons via an AI system get a primitive that emits the "you are interacting with an AI system" notice + audits the emission. `placement` opts: `"first-message"` (default — emit on first contact only, tracked via `session.aiDisclosureEmitted`), `"always"` (every response), `"on-request"` (operator wires their own trigger). Returns `{ text, language, jurisdiction, placement, shouldEmit, article, regulation }` — `shouldEmit` is the operator-consumable boolean for response-wire-up logic. · *`b.ai.disclosure.deepfake(content, opts)` — Art. 50(4) synthetic-content label* — Operators emitting model-generated or model-manipulated content get a primitive that returns both the visible label markup AND the machine-readable metadata payload. `contentType: "image" | "audio" | "video" | "text"` is required; `placement: "label" | "metadata" | "both"` (default `"both"`) controls what the primitive populates. The metadata payload includes `schema: "c2pa-v1.4-ready"` — the v0.12.21 `b.contentCredentials` C2PA adapter will consume this schema field when it lands. `crossWalk` array carries `["eu-ai-act/Art. 50(4)"]` plus the per-jurisdiction reference (AB-853 §22949.91 / CAC GenAI Art. 12). · *`b.ai.disclosure.emotion(opts)` — Art. 50(3) emotion-recognition / biometric-categorisation notice* — Operators deploying emotion-recognition or biometric-categorisation systems get the consent-flow notice primitive. `systemType: "emotion" | "biometric-categorisation"` (default `"emotion"`) selects which Art. 50(3) sub-obligation applies. Returns the notice payload + emits an `ai-act/emotion-disclosure-applied` audit event. · *Cross-jurisdiction cross-walk: EU + US-CA + China in a single primitive* — The `opts.jurisdiction` opt accepts `"eu"` (default — Regulation (EU) 2024/1689), `"us-ca"` (California AB-853 effective 2026), or `"cn"` (China CAC GenAI Measures). The chatbot + deepfake primitives both honour the cross-walk: the deepfake response's `crossWalk` array carries every jurisdiction-specific legal reference the same emission satisfies, so operators serving multi-region traffic emit one notice + audit one event + reference all applicable regimes. **Security:** *Drop-silent audit emission preserves the disclosure path under audit-bus failure* — If `opts.audit` is supplied but its `safeEmit` throws (network bus down, audit-sign chain malformed), the disclosure primitive still returns the user-facing notice payload. The Art. 50 obligation is the user-facing notice itself; the audit emission is a parallel best-effort chain-of-custody record. Refusing the disclosure to defend the audit chain would fail the wrong direction — the regulatory contract is satisfied by emitting the notice. Matches the framework's `audit.safeEmit` drop-silent contract for hot-path observability sinks. **Migration:** *C2PA manifest emission lands in v0.12.21* — The deepfake primitive's metadata payload includes a `schema: "c2pa-v1.4-ready"` field that the v0.12.21 `b.contentCredentials` adapter will consume. Operators emitting image / audio / video for v0.12.12-0.12.20 get the label markup + structured metadata; the actual C2PA manifest (signed JUMBF assertion chain) is the next composition layer.
|
|
12
14
|
|
|
13
15
|
- v0.12.11 (2026-05-23) — **`b.archive.wrapWithPassphrase` + `b.archive.unwrapWithPassphrase` — Argon2id + XChaCha20-Poly1305 archive envelope + `b.backup` `cryptoStrategy: "passphrase"` with HIPAA / PCI-DSS 128-bit entropy floor.** Passphrase wrap lands as the second `b.archive` envelope strategy alongside v0.12.10's recipient wrap. `b.archive.wrapWithPassphrase(bytes, { passphrase, minEntropyBits })` produces a `BAWPP`-prefixed envelope under Argon2id (RFC 9106; framework-default 64 MiB / 3 iterations / 4 parallelism) key derivation with XChaCha20-Poly1305 AEAD; each envelope carries its own fresh salt in the wire format (5-byte magic + 1-byte version + 1-byte saltLen + salt + 24-byte nonce + ciphertext+tag) so KDF parameters can rotate in future minors without per-envelope version bumps. `b.archive.unwrapWithPassphrase(sealed, { passphrase })` verifies the `BAWPP` header before any Argon2id compute so non-envelope inputs fail with `archive-wrap/bad-magic` rather than burning the KDF on bad bytes. `b.backup.bundleAdapterStorage({ cryptoStrategy: "passphrase", passphrase })` composes the wrap layer transparently — bundle bytes hitting the adapter's `writeFile` are an opaque passphrase-derived envelope. Default `passphraseMinEntropyBits: 80` matches OWASP strong-password guidance; HIPAA + PCI-DSS postures raise the floor to 128 bits automatically (matching the framework's existing crypto-grade-password discipline for sealed-storage). The recipient strategy from v0.12.10 + passphrase strategy from v0.12.11 + plaintext strategy from v0.12.7 cover the operator's posture matrix: HIPAA / PCI-DSS pick recipient or passphrase; non-regulated deployments may stay on `"none"` when the storage layer is itself the protective boundary. **Added:** *`b.archive.wrapWithPassphrase(bytes, { passphrase, minEntropyBits })` — Argon2id-derived archive envelope* — Composes `b.backupCrypto.encryptWithFreshSalt(bytes, passphrase)` (Argon2id KDF + XChaCha20-Poly1305 AEAD, fresh per-envelope salt) and prepends a 7-byte `BAWPP` envelope header (5-byte magic + 1-byte version + 1-byte saltLen) so format sniffers can identify passphrase wrap output without trial KDF work. Entropy estimate uses observed-alphabet bit-count (the standard NIST/OWASP character-class approximation). `minEntropyBits` defaults to 80; the gate refuses upfront with `archive-wrap/weak-passphrase` when the estimate falls short. · *`b.archive.unwrapWithPassphrase(sealed, { passphrase })` — inverse with magic-check upfront* — Verifies the 7-byte `BAWPP` header (magic + version + saltLen) before any cryptographic work so non-envelope inputs (raw archives, recipient-wrap envelopes, truncated buffers) fail with `archive-wrap/bad-magic` / `archive-wrap/bad-version` / `archive-wrap/truncated-envelope` rather than wasting Argon2id compute. Routes through `b.backupCrypto.decryptWithPassphrase(encrypted, passphrase, saltHex)` so the framework's locked Argon2id parameters apply. · *`b.backup.bundleAdapterStorage({ cryptoStrategy: "passphrase", passphrase })` — Argon2id-keyed bundle storage* — Composes `b.archive.wrapWithPassphrase` transparently — every `writeBundle` payload is wrapped before `adapter.writeFile`; every `readBundle` payload is unwrapped after `adapter.readFile`. The `passphraseMinEntropyBits` opt defaults to 80 (OWASP strong-password floor); HIPAA + PCI-DSS postures raise the floor to 128 bits automatically. Passphrase + directory format combination refused upfront (same contract as recipient + directory). Wire-format envelope on disk is opaque ciphertext — no information leakage about archive contents through the storage adapter. · *HIPAA + PCI-DSS postures raise entropy floor to 128 bits under passphrase strategy* — `bundleAdapterStorage({ posture: "hipaa", cryptoStrategy: "passphrase", passphrase })` enforces `passphraseMinEntropyBits >= 128` regardless of the operator-supplied opt. The 128-bit floor matches the framework's existing crypto-grade-password discipline for sealed-storage cells. Operators sourcing passphrases from a CSPRNG (`b.crypto.generateBytes(16).toString("base64url")` → ~128 bits) pass without issue; operators typing dictionary phrases trip the gate. **Security:** *Magic-check before KDF work — non-envelope inputs can't burn Argon2id compute* — Adversarial inputs that look like passphrase envelopes but aren't (random bytes, recipient envelopes, raw archives) fail at byte 0-4 (magic check) rather than after a 64 MiB Argon2id round. Operators handing user-supplied bundles to readBundle on a server with concurrent load get bounded refusal latency rather than worst-case KDF compute under a chosen-bytes attack.
|
package/lib/backup/index.js
CHANGED
|
@@ -1497,6 +1497,194 @@ bundleAdapterStorage.fsAdapter = function (fsOpts) {
|
|
|
1497
1497
|
};
|
|
1498
1498
|
};
|
|
1499
1499
|
|
|
1500
|
+
// ---- v0.12.13: objectStoreAdapter ----------------------------------------
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* @primitive b.backup.bundleAdapterStorage.objectStoreAdapter
|
|
1504
|
+
* @signature b.backup.bundleAdapterStorage.objectStoreAdapter(client, opts?)
|
|
1505
|
+
* @since 0.12.13
|
|
1506
|
+
* @status stable
|
|
1507
|
+
* @related b.backup.bundleAdapterStorage, b.objectStore
|
|
1508
|
+
*
|
|
1509
|
+
* Wraps a `b.objectStore`-shaped client into the
|
|
1510
|
+
* `{ writeFile, readFile, listKeys, deleteKey, hasKey }` adapter
|
|
1511
|
+
* contract that `bundleAdapterStorage` consumes. The client must
|
|
1512
|
+
* expose `put(key, body) → Promise<{ size }>`, `get(key) →
|
|
1513
|
+
* Promise<Buffer>`, `head(key) → Promise<{ size, ... }>`,
|
|
1514
|
+
* `delete(key) → Promise<boolean>`, and `list(prefix, opts?) →
|
|
1515
|
+
* Promise<{ items: [{ key, size, ... }], truncated }>` — the
|
|
1516
|
+
* shape produced by `b.objectStore.buildBackend({ protocol: ... })`
|
|
1517
|
+
* for the local / SigV4 / GCS / Azure-Blob backends.
|
|
1518
|
+
*
|
|
1519
|
+
* `opts.prefix` namespaces every key under a fixed root inside the
|
|
1520
|
+
* bucket — operators sharing a bucket across multiple deployments
|
|
1521
|
+
* pass distinct prefixes so listings stay scoped.
|
|
1522
|
+
*
|
|
1523
|
+
* `opts.list` is the operator-tunable `{ maxResults, ... }` pass-
|
|
1524
|
+
* through forwarded to the underlying `client.list` call (defaults
|
|
1525
|
+
* to whatever the backend's `list` defaults to — typically 1000).
|
|
1526
|
+
*
|
|
1527
|
+
* Closes the v0.12.10 deferral: "S3 / MinIO / Azure / GCS-backed
|
|
1528
|
+
* backups" promised since v0.11.2 JSDoc.
|
|
1529
|
+
*
|
|
1530
|
+
* @opts
|
|
1531
|
+
* prefix: string, // namespace every key under this prefix in the bucket
|
|
1532
|
+
* list: { maxResults: number }, // forwarded to client.list opts
|
|
1533
|
+
*
|
|
1534
|
+
* @example
|
|
1535
|
+
* var client = b.objectStore.buildBackend({
|
|
1536
|
+
* protocol: "local",
|
|
1537
|
+
* rootDir: "/var/backups",
|
|
1538
|
+
* });
|
|
1539
|
+
* var storage = b.backup.bundleAdapterStorage({
|
|
1540
|
+
* adapter: b.backup.bundleAdapterStorage.objectStoreAdapter(client),
|
|
1541
|
+
* format: "tar.gz",
|
|
1542
|
+
* cryptoStrategy: "recipient",
|
|
1543
|
+
* recipient: pair,
|
|
1544
|
+
* });
|
|
1545
|
+
* // bundle bytes hit the object-store backend's put(); restore
|
|
1546
|
+
* // path composes through unwrap + read.gz + read.tar.
|
|
1547
|
+
*/
|
|
1548
|
+
bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
1549
|
+
if (!client || typeof client !== "object") {
|
|
1550
|
+
throw new BackupError("backup/bad-adapter",
|
|
1551
|
+
"objectStoreAdapter: client is required (a b.objectStore-shaped object with put / get / head / delete / list)");
|
|
1552
|
+
}
|
|
1553
|
+
var required = ["put", "get", "head", "delete", "list"];
|
|
1554
|
+
for (var i = 0; i < required.length; i += 1) {
|
|
1555
|
+
if (typeof client[required[i]] !== "function") {
|
|
1556
|
+
throw new BackupError("backup/bad-adapter",
|
|
1557
|
+
"objectStoreAdapter: client missing method '" + required[i] + "'");
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
osOpts = osOpts || {};
|
|
1561
|
+
var prefix = "";
|
|
1562
|
+
if (osOpts.prefix !== undefined && osOpts.prefix !== null) {
|
|
1563
|
+
if (typeof osOpts.prefix !== "string") {
|
|
1564
|
+
throw new BackupError("backup/bad-arg",
|
|
1565
|
+
"objectStoreAdapter: opts.prefix must be a string");
|
|
1566
|
+
}
|
|
1567
|
+
if (osOpts.prefix.indexOf("..") !== -1 || osOpts.prefix.indexOf("\u0000") !== -1) {
|
|
1568
|
+
throw new BackupError("backup/bad-arg",
|
|
1569
|
+
"objectStoreAdapter: opts.prefix contains traversal segment or NUL byte");
|
|
1570
|
+
}
|
|
1571
|
+
// Strip trailing slashes without a backtracking regex (CodeQL
|
|
1572
|
+
// js/polynomial-redos flagged `/\/+$/`, which is linear in
|
|
1573
|
+
// practice but flagged conservatively). Walk back from the end
|
|
1574
|
+
// and slice once.
|
|
1575
|
+
var endIdx = osOpts.prefix.length;
|
|
1576
|
+
while (endIdx > 0 && osOpts.prefix.charCodeAt(endIdx - 1) === 0x2f) endIdx -= 1; // 0x2f = "/"
|
|
1577
|
+
prefix = osOpts.prefix.slice(0, endIdx);
|
|
1578
|
+
if (prefix.length > 0) prefix += "/";
|
|
1579
|
+
}
|
|
1580
|
+
var listOpts = osOpts.list || {};
|
|
1581
|
+
|
|
1582
|
+
function _scopedKey(key) {
|
|
1583
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
1584
|
+
throw new BackupError("backup/bad-key",
|
|
1585
|
+
"objectStoreAdapter: key must be a non-empty string");
|
|
1586
|
+
}
|
|
1587
|
+
if (key.indexOf("..") !== -1 || key.indexOf("\u0000") !== -1) {
|
|
1588
|
+
throw new BackupError("backup/bad-key",
|
|
1589
|
+
"objectStoreAdapter: key contains traversal segment or NUL byte");
|
|
1590
|
+
}
|
|
1591
|
+
return prefix + key;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
return {
|
|
1595
|
+
async writeFile(key, bytes) {
|
|
1596
|
+
if (!Buffer.isBuffer(bytes) && !(bytes instanceof Uint8Array)) {
|
|
1597
|
+
throw new BackupError("backup/bad-arg",
|
|
1598
|
+
"objectStoreAdapter.writeFile: bytes must be a Buffer or Uint8Array");
|
|
1599
|
+
}
|
|
1600
|
+
await client.put(_scopedKey(key), Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes));
|
|
1601
|
+
},
|
|
1602
|
+
async readFile(key) {
|
|
1603
|
+
var scoped = _scopedKey(key);
|
|
1604
|
+
try {
|
|
1605
|
+
var body = await client.get(scoped);
|
|
1606
|
+
return Buffer.isBuffer(body) ? body : Buffer.from(body);
|
|
1607
|
+
} catch (e) {
|
|
1608
|
+
// b.objectStore surfaces NOT_FOUND via the framework's
|
|
1609
|
+
// err.code === "NOT_FOUND" convention — translate to the
|
|
1610
|
+
// backup adapter contract's no-key error.
|
|
1611
|
+
if (e && (e.code === "NOT_FOUND" || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
1612
|
+
throw new BackupError("backup/no-key",
|
|
1613
|
+
"objectStoreAdapter: key not found: " + JSON.stringify(key));
|
|
1614
|
+
}
|
|
1615
|
+
throw e;
|
|
1616
|
+
}
|
|
1617
|
+
},
|
|
1618
|
+
async listKeys(keyPrefix) {
|
|
1619
|
+
// _scopedKey rejects empty strings; for list(prefix) we want
|
|
1620
|
+
// to allow listing the whole bundle root. Compose the
|
|
1621
|
+
// prefix manually so `listKeys("")` enumerates everything
|
|
1622
|
+
// under the operator-supplied namespace.
|
|
1623
|
+
var realScoped = prefix + (keyPrefix || "");
|
|
1624
|
+
// Codex P1 on v0.12.13 PR #164 — object-store backends page
|
|
1625
|
+
// results (default 1000 keys). Without continuation, listKeys
|
|
1626
|
+
// silently dropped bundles past page 1 — listBundles missed
|
|
1627
|
+
// them, deleteBundle skipped them. Follow the
|
|
1628
|
+
// truncated / continuationToken contract until every page
|
|
1629
|
+
// is consumed. PAGINATION_CAP guards against a runaway
|
|
1630
|
+
// server returning truncated:true forever (defense-in-depth;
|
|
1631
|
+
// shipped backends honour the contract).
|
|
1632
|
+
var PAGINATION_CAP = 1000; // allow:raw-byte-literal — page count cap, not byte count
|
|
1633
|
+
var out = [];
|
|
1634
|
+
var token = null;
|
|
1635
|
+
var pages = 0;
|
|
1636
|
+
do {
|
|
1637
|
+
var pageOpts = Object.assign({}, listOpts);
|
|
1638
|
+
if (token) pageOpts.continuationToken = token;
|
|
1639
|
+
var result = await client.list(realScoped, pageOpts);
|
|
1640
|
+
var items = result && result.items ? result.items : (Array.isArray(result) ? result : []);
|
|
1641
|
+
for (var i = 0; i < items.length; i += 1) {
|
|
1642
|
+
var k = typeof items[i] === "string" ? items[i] : items[i].key;
|
|
1643
|
+
if (typeof k !== "string") continue;
|
|
1644
|
+
if (prefix.length > 0 && k.indexOf(prefix) === 0) {
|
|
1645
|
+
out.push(k.slice(prefix.length));
|
|
1646
|
+
} else {
|
|
1647
|
+
out.push(k);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
token = result && result.continuationToken ? result.continuationToken : null;
|
|
1651
|
+
if (!result || result.truncated !== true) break;
|
|
1652
|
+
if (!token) break; // truncated:true without continuationToken — stop to avoid spin
|
|
1653
|
+
pages += 1;
|
|
1654
|
+
if (pages > PAGINATION_CAP) {
|
|
1655
|
+
throw new BackupError("backup/list-pagination-runaway",
|
|
1656
|
+
"objectStoreAdapter.listKeys: backend returned >" + PAGINATION_CAP +
|
|
1657
|
+
" pages without exhausting; refusing to spin (operator should narrow the prefix or raise opts.list.maxResults)");
|
|
1658
|
+
}
|
|
1659
|
+
} while (true);
|
|
1660
|
+
return out;
|
|
1661
|
+
},
|
|
1662
|
+
async deleteKey(key) {
|
|
1663
|
+
try {
|
|
1664
|
+
await client.delete(_scopedKey(key));
|
|
1665
|
+
} catch (e) {
|
|
1666
|
+
// drop-silent on NOT_FOUND — adapter contract is idempotent
|
|
1667
|
+
// delete (fsAdapter same shape).
|
|
1668
|
+
if (e && (e.code === "NOT_FOUND" || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
throw e;
|
|
1672
|
+
}
|
|
1673
|
+
},
|
|
1674
|
+
async hasKey(key) {
|
|
1675
|
+
try {
|
|
1676
|
+
await client.head(_scopedKey(key));
|
|
1677
|
+
return true;
|
|
1678
|
+
} catch (e) {
|
|
1679
|
+
if (e && (e.code === "NOT_FOUND" || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
1680
|
+
return false;
|
|
1681
|
+
}
|
|
1682
|
+
throw e;
|
|
1683
|
+
}
|
|
1684
|
+
},
|
|
1685
|
+
};
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1500
1688
|
// ---- v0.12.8: migrate ----------------------------------------------------
|
|
1501
1689
|
|
|
1502
1690
|
/**
|
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:ab401616-3671-455e-8dbd-edc2385efd48",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-24T02:05:49.624Z",
|
|
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.12.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.12.13",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.12.
|
|
25
|
+
"version": "0.12.13",
|
|
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.12.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.12.13",
|
|
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.12.
|
|
57
|
+
"ref": "@blamejs/core@0.12.13",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|