@blamejs/blamejs-shop 0.1.32 → 0.1.33
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/README.md +2 -0
- package/lib/admin.js +18 -10
- package/lib/asset-manifest.json +17 -5
- package/lib/storefront.js +326 -44
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +8 -0
- package/lib/vendor/blamejs/README.md +3 -1
- package/lib/vendor/blamejs/api-snapshot.json +99 -2
- package/lib/vendor/blamejs/index.js +3 -0
- package/lib/vendor/blamejs/lib/crypto-oprf.js +110 -0
- package/lib/vendor/blamejs/lib/network-tsig.js +404 -0
- package/lib/vendor/blamejs/lib/network.js +1 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +44 -9
- package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +19 -0
- package/lib/vendor/blamejs/lib/worm.js +246 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.x.json +1844 -0
- package/lib/vendor/blamejs/release-notes/v0.13.0.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.13.1.json +18 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +11 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tsig.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +2 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +3 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/worm.test.js +126 -0
- package/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.12.0.json +0 -64
- package/lib/vendor/blamejs/release-notes/v0.12.1.json +0 -32
- package/lib/vendor/blamejs/release-notes/v0.12.10.json +0 -65
- package/lib/vendor/blamejs/release-notes/v0.12.11.json +0 -39
- package/lib/vendor/blamejs/release-notes/v0.12.12.json +0 -48
- package/lib/vendor/blamejs/release-notes/v0.12.13.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.12.14.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.15.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.16.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.17.json +0 -22
- package/lib/vendor/blamejs/release-notes/v0.12.18.json +0 -22
- package/lib/vendor/blamejs/release-notes/v0.12.19.json +0 -22
- package/lib/vendor/blamejs/release-notes/v0.12.2.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.12.20.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.21.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.22.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.23.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.24.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.25.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.26.json +0 -30
- package/lib/vendor/blamejs/release-notes/v0.12.27.json +0 -26
- package/lib/vendor/blamejs/release-notes/v0.12.28.json +0 -26
- package/lib/vendor/blamejs/release-notes/v0.12.29.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.12.3.json +0 -23
- package/lib/vendor/blamejs/release-notes/v0.12.30.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.31.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.32.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.33.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.12.34.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.35.json +0 -22
- package/lib/vendor/blamejs/release-notes/v0.12.36.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.37.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.38.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.39.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.4.json +0 -19
- package/lib/vendor/blamejs/release-notes/v0.12.40.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.41.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.42.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.43.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.44.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.45.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.46.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.47.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.48.json +0 -22
- package/lib/vendor/blamejs/release-notes/v0.12.49.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.12.5.json +0 -40
- package/lib/vendor/blamejs/release-notes/v0.12.50.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.51.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.52.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.53.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.54.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.55.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.56.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.57.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.58.json +0 -22
- package/lib/vendor/blamejs/release-notes/v0.12.6.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.12.60.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.61.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.62.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.63.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.64.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.65.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.66.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.12.68.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.69.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.12.7.json +0 -86
- package/lib/vendor/blamejs/release-notes/v0.12.8.json +0 -81
- package/lib/vendor/blamejs/release-notes/v0.12.9.json +0 -61
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.10",
|
|
4
|
-
"date": "2026-05-23",
|
|
5
|
-
"headline": "`b.archive.wrap` + `b.archive.unwrap` — recipient-encrypted archive envelopes (Flavor 1) + `b.backup` `cryptoStrategy: \"recipient\"` + HIPAA/PCI-DSS posture refusal",
|
|
6
|
-
"summary": "Flavor 1 lands as the whole-archive recipient-wrap substrate. `b.archive.wrap(bytes, { recipient })` produces a sealed envelope under the framework's hybrid PQC seal (ML-KEM-1024 + P-384 ECDH + SHAKE256 + XChaCha20-Poly1305) prefixed with a 6-byte `BAWRP` archive-wrap header so format sniffers can identify wrap envelopes without trial decryption. `b.archive.unwrap(sealed, { recipient })` is the inverse with magic-check upfront so non-envelope inputs throw `archive-wrap/bad-magic` rather than a crypto-level error. Recipient strategies: static keypair (`{ publicKey, ecPublicKey }`) and peer-cert (`{ peerCertDer, peerKemPubkey }`); the tenant strategy lands in v0.12.11 alongside the backup-crypto refactor + per-tenant key resolution. `b.backup.bundleAdapterStorage({ cryptoStrategy: \"recipient\", recipient })` composes the wrap/unwrap layer transparently: the bytes hitting the adapter's `writeFile` are a `BAWRP`-prefixed envelope, never the raw tar / tar.gz / directory bundle. HIPAA + PCI-DSS postures refuse `cryptoStrategy: \"none\"` upfront with `backup/posture-requires-encryption` — the storage adapter cannot itself satisfy the encryption-at-rest requirement; the recipient envelope is the framework-side gate. Flavor 2 (per-entry ZIP wrap with the 0xBADC extra-field marker) and the backup-crypto refactor into `lib/_crypto-base.js` ship in v0.12.11.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.archive.wrap(bytes, { recipient })` — recipient-encrypted archive envelope",
|
|
13
|
-
"body": "Composes `b.crypto.encrypt` (or `b.crypto.encryptEnvelopeAsCertPeer` for the peer-cert strategy) under the framework's hybrid PQC seal. The output is a Buffer carrying a 6-byte `BAWRP` archive-wrap header (5-byte magic + 1-byte version) followed by the base64-encoded envelope bytes. Recipient strategies: `{ publicKey, ecPublicKey }` for the static-keypair path (ML-KEM-1024 PEM + P-384 ECDH PEM); `{ peerCertDer, peerKemPubkey }` for the peer-cert path (extracts the P-384 half from the cert per `b.crypto.encryptEnvelopeAsCertPeer`). `\"tenant\"` returns `archive-wrap/tenant-strategy-deferred` upfront — that strategy lands in v0.12.11 with the per-tenant key resolution."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`b.archive.unwrap(sealed, { recipient })` — inverse with upfront magic check",
|
|
17
|
-
"body": "Verifies the 6-byte `BAWRP` header before any cryptographic work so non-envelope inputs (raw archives, other-magic envelopes, truncated buffers) fail with `archive-wrap/bad-magic` / `archive-wrap/bad-version` rather than a downstream `crypto/*` error. Routes through `b.crypto.decrypt(envelope, recipient, { raw: true })` so binary archive payloads (gzip, ZIP, tar) round-trip losslessly — `raw: true` is the contract that preserves bytes vs the default utf-8 decoding."
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"title": "`b.backup.bundleAdapterStorage({ cryptoStrategy: \"recipient\", recipient })` — opt-in envelope storage",
|
|
21
|
-
"body": "`cryptoStrategy: \"none\"` (default, v0.12.7-9 behaviour) writes plaintext bundle bytes to the adapter — safe for storage layers that are themselves the protective boundary (S3 SSE, disk-encrypted hosts). `cryptoStrategy: \"recipient\"` requires `opts.recipient` and wraps every `writeBundle` payload through `b.archive.wrap` before `adapter.writeFile`; `readBundle` unwraps after `adapter.readFile`. The wrap layer sits OUTSIDE the gz / tar layers so the bundle on disk is opaque ciphertext under the operator-controlled recipient key. Passphrase strategy is deferred to v0.12.11 alongside the `_crypto-base.js` refactor."
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"title": "HIPAA + PCI-DSS posture refuses plaintext bundles",
|
|
25
|
-
"body": "`bundleAdapterStorage({ posture: \"hipaa\" })` (or `\"pci-dss\"`) refuses `cryptoStrategy: \"none\"` upfront with `backup/posture-requires-encryption` — adapter-storage's plaintext default cannot itself satisfy encryption-at-rest requirements. Operators under these postures pass `cryptoStrategy: \"recipient\"` + a recipient key. The refusal message includes the posture name + the strategy that fails so audit-trail operators see exactly which gate blocked the call."
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"heading": "Security",
|
|
31
|
-
"items": [
|
|
32
|
-
{
|
|
33
|
-
"title": "Wrap envelope is the framework's hybrid PQC seal — ML-KEM-1024 + P-384 ECDH + SHAKE256 + XChaCha20-Poly1305",
|
|
34
|
-
"body": "Defence-in-depth posture: a CRQC against ML-KEM-1024 alone still has to defeat the classical P-384 ECDH leg; a future ECDH break alone still has to defeat ML-KEM-1024. The 4-byte envelope header (magic + KEM ID + cipher ID + KDF ID) is bound as AEAD AAD so a header-substitution attack fails Poly1305 verification. `b.archive.wrap` prepends a separate 6-byte archive-wrap header BEFORE the base64 envelope so format sniffing can identify wrap output without trial decryption — non-envelope inputs are refused at byte 0-4 (magic check) instead of after fruitless decapsulation work."
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"heading": "Detectors",
|
|
40
|
-
"items": [
|
|
41
|
-
{
|
|
42
|
-
"title": "`backup-adapter-storage-without-posture-check` — postures that mandate encryption must propagate to `cryptoStrategy`",
|
|
43
|
-
"body": "When a primitive that wires `b.backup.bundleAdapterStorage` carries a `posture:` opt drawn from the HIPAA / PCI-DSS / etc. set, the same code path must propagate `cryptoStrategy: \"recipient\"` (or refuse before reaching writeBundle). The detector matches `bundleAdapterStorage({ ... posture: ... })` invocations in `lib/` and requires a matching `cryptoStrategy` opt; missing it surfaces during the codebase-patterns gate so a future caller can't silently drop the contract."
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"heading": "Migration",
|
|
49
|
-
"items": [
|
|
50
|
-
{
|
|
51
|
-
"title": "Flavor 2 — per-entry ZIP recipient wrap with 0xBADC extra-field",
|
|
52
|
-
"body": "Per-entry encryption inside the carrier ZIP (method=STORE with the encrypted bytes as the stored payload + a 0xBADC user-defined-range extra-field marker carrying the recipient hint). Inspect-without-decrypt is the operator value: entry list + name-safety gating happens BEFORE any key resolution. Lands in v0.12.11 alongside the backup-crypto refactor."
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"title": "`lib/_crypto-base.js` refactor — backup-crypto, Flavor 1, Flavor 2 share substrate",
|
|
56
|
-
"body": "The legacy per-file Argon2id + XChaCha20-Poly1305 path in `lib/backup/crypto.js` gets factored into a private `_crypto-base.js` helper so all three encryption flavors compose the same primitive set. No operator-visible API change; closes the each-feature-rolls-its-own-crypto smell."
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"title": "`cryptoStrategy: \"passphrase\"` + tenant strategy",
|
|
60
|
-
"body": "Passphrase strategy on `bundleAdapterStorage` (Argon2id-derived key + XChaCha20-Poly1305) and the `\"tenant\"` recipient string (composes `b.vault.derivedKey({ tenant, purpose: \"archive-wrap\" })`) both ship in v0.12.11. The v0.12.10 surface is the recipient substrate; v0.12.11 lights up the per-tenant + passphrase strategies that consume it."
|
|
61
|
-
}
|
|
62
|
-
]
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.11",
|
|
4
|
-
"date": "2026-05-23",
|
|
5
|
-
"headline": "`b.archive.wrapWithPassphrase` + `b.archive.unwrapWithPassphrase` — Argon2id + XChaCha20-Poly1305 archive envelope + `b.backup` `cryptoStrategy: \"passphrase\"` with HIPAA / PCI-DSS 128-bit entropy floor",
|
|
6
|
-
"summary": "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.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.archive.wrapWithPassphrase(bytes, { passphrase, minEntropyBits })` — Argon2id-derived archive envelope",
|
|
13
|
-
"body": "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."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`b.archive.unwrapWithPassphrase(sealed, { passphrase })` — inverse with magic-check upfront",
|
|
17
|
-
"body": "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."
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"title": "`b.backup.bundleAdapterStorage({ cryptoStrategy: \"passphrase\", passphrase })` — Argon2id-keyed bundle storage",
|
|
21
|
-
"body": "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."
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"title": "HIPAA + PCI-DSS postures raise entropy floor to 128 bits under passphrase strategy",
|
|
25
|
-
"body": "`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."
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"heading": "Security",
|
|
31
|
-
"items": [
|
|
32
|
-
{
|
|
33
|
-
"title": "Magic-check before KDF work — non-envelope inputs can't burn Argon2id compute",
|
|
34
|
-
"body": "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."
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.12",
|
|
4
|
-
"date": "2026-05-23",
|
|
5
|
-
"headline": "`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",
|
|
6
|
-
"summary": "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.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.ai.disclosure.chatbot(session, opts)` — Art. 50(1) first-contact disclosure",
|
|
13
|
-
"body": "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."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`b.ai.disclosure.deepfake(content, opts)` — Art. 50(4) synthetic-content label",
|
|
17
|
-
"body": "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)."
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"title": "`b.ai.disclosure.emotion(opts)` — Art. 50(3) emotion-recognition / biometric-categorisation notice",
|
|
21
|
-
"body": "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."
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"title": "Cross-jurisdiction cross-walk: EU + US-CA + China in a single primitive",
|
|
25
|
-
"body": "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."
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"heading": "Security",
|
|
31
|
-
"items": [
|
|
32
|
-
{
|
|
33
|
-
"title": "Drop-silent audit emission preserves the disclosure path under audit-bus failure",
|
|
34
|
-
"body": "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."
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"heading": "Migration",
|
|
40
|
-
"items": [
|
|
41
|
-
{
|
|
42
|
-
"title": "C2PA manifest emission lands in v0.12.21",
|
|
43
|
-
"body": "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."
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
}
|
|
47
|
-
]
|
|
48
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.13",
|
|
4
|
-
"date": "2026-05-23",
|
|
5
|
-
"headline": "`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",
|
|
6
|
-
"summary": "`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.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.backup.bundleAdapterStorage.objectStoreAdapter(client, opts?)` — object-store-backed bundle storage",
|
|
13
|
-
"body": "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."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`opts.prefix` — per-deployment key namespacing inside the bucket",
|
|
17
|
-
"body": "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."
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"heading": "Security",
|
|
23
|
-
"items": [
|
|
24
|
-
{
|
|
25
|
-
"title": "Key path validation — traversal + NUL byte refusal at every adapter call",
|
|
26
|
-
"body": "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."
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.14",
|
|
4
|
-
"date": "2026-05-23",
|
|
5
|
-
"headline": "`b.archive.sniffEnvelope(bytes)` — identify recipient vs passphrase vs raw payload without attempting decryption",
|
|
6
|
-
"summary": "Small helper closing a gap in the archive-wrap surface. `b.archive.sniffEnvelope(bytes)` reads the first 5 bytes of a buffer and returns one of `\"recipient\"` (v0.12.10 BAWRP envelope), `\"passphrase\"` (v0.12.11 BAWPP envelope), or `\"none\"` (raw payload or unrelated bytes). The sniff does NO cryptographic work — no Argon2id round, no decapsulation, no allocation beyond a 5-byte ASCII compare — so it's safe to call on adversarial input. Operators dispatching between unwrap paths get a clean predicate instead of trial-decrypting under multiple key candidates.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.archive.sniffEnvelope(bytes)` — magic-byte envelope identifier",
|
|
13
|
-
"body": "Returns `\"recipient\"` (BAWRP / v0.12.10 hybrid PQC envelope), `\"passphrase\"` (BAWPP / v0.12.11 Argon2id + XChaCha20 envelope), or `\"none\"` (raw archive bytes / unrelated payload). Accepts Buffer + Uint8Array; non-buffer / null / undefined / empty-buffer inputs return `\"none\"` upfront. Operators wire the result into a switch that dispatches to the matching unwrap primitive — no trial decryption, no per-key candidate attempts."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.15",
|
|
4
|
-
"date": "2026-05-23",
|
|
5
|
-
"headline": "`b.safeArchive.extract` auto-unwraps v0.12.10 recipient and v0.12.11 passphrase envelopes inline",
|
|
6
|
-
"summary": "The safeArchive orchestrator's `format: \"auto\"` sniffer recognises `BAWRP` (v0.12.10 recipient) and `BAWPP` (v0.12.11 passphrase) envelope magics and routes through `b.archive.unwrap` / `b.archive.unwrapWithPassphrase` inline before re-sniffing the inner format. Operators pass `opts.recipient` (or `opts.passphrase`) alongside `source` + `destination` and get a single extract() call regardless of envelope shape. Missing the matching key opt surfaces a structured `safe-archive/no-recipient-for-wrap` / `safe-archive/no-passphrase-for-wrap` refusal upfront rather than a downstream crypto error.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.safeArchive.extract` auto-unwraps wrap envelopes",
|
|
13
|
-
"body": "The sniffer at byte 0-4 recognises `BAWRP` (returns `format: \"wrap-recipient\"`) and `BAWPP` (returns `format: \"wrap-passphrase\"`). The extract path collects the sealed adapter into a Buffer, routes through `b.archive.unwrap` (recipient) or `b.archive.unwrapWithPassphrase` (passphrase), wraps the inner bytes in a buffer adapter, re-sniffs the inner format, and dispatches to the appropriate `b.archive.read.*` reader. A wrap-around-tar.gz envelope round-trips through wrap → unwrap → gunzip → untar with no operator intervention beyond passing the key opt."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"heading": "Security",
|
|
19
|
-
"items": [
|
|
20
|
-
{
|
|
21
|
-
"title": "Missing-key opt refused upfront with structured error",
|
|
22
|
-
"body": "When the sniffer identifies a wrap envelope but the operator hasn't supplied `opts.recipient` (BAWRP) or `opts.passphrase` (BAWPP), extract refuses with `safe-archive/no-recipient-for-wrap` / `safe-archive/no-passphrase-for-wrap` BEFORE any decryption attempt. Operators wiring extract behind an HTTP boundary get a typed refusal instead of a leaked crypto-level error."
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.16",
|
|
4
|
-
"date": "2026-05-23",
|
|
5
|
-
"headline": "`b.safeArchive.inspect` auto-unwraps wrap envelopes (parallel to the v0.12.15 extract path)",
|
|
6
|
-
"summary": "Mirrors the v0.12.15 auto-unwrap support into `b.safeArchive.inspect`. Operators enumerating entries of a sealed archive get a single inspect() call regardless of envelope shape — pass `opts.recipient` or `opts.passphrase` alongside `source` and the orchestrator unwraps inline before walking the inner format. Missing-key opt surfaces a structured `safe-archive/no-recipient-for-wrap` / `safe-archive/no-passphrase-for-wrap` refusal upfront. Carries the v0.12.15 P1 + P2 fixes (close original source before replacing + forward opts.signal to inner buffer adapter) into the inspect path so the same descriptor-leak + abort-propagation contracts hold.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.safeArchive.inspect` auto-unwraps `BAWRP` + `BAWPP` envelopes",
|
|
13
|
-
"body": "The orchestrator's `format: \"auto\"` sniffer recognises the wrap magics and routes through `b.archive.unwrap` / `b.archive.unwrapWithPassphrase` inline. After unwrap, the inner bytes are wrapped in a buffer adapter + re-sniffed; the resulting summary carries the INNER `format` (`\"tar\"` / `\"zip\"` / etc.) — operators querying `summary.format` see the carrier format, not `\"wrap-recipient\"`. Entry enumeration walks the inner archive after a single key-derivation pass; no temporary file lands on disk."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.17",
|
|
4
|
-
"date": "2026-05-23",
|
|
5
|
-
"headline": "`bundleAdapterStorage.bundleInfo` + `listBundles.format` — per-bundle introspection for envelope kind + format without restore",
|
|
6
|
-
"summary": "Two introspection additions on `b.backup.bundleAdapterStorage`. `listBundles()` now returns the inferred `format` (`\"tar\"` / `\"tar.gz\"` / `\"directory\"`) per bundle from the storage key suffix — no byte read. `storage.bundleInfo(bundleId)` returns `{ bundleId, format, envelopeKind, sizeBytes }` where `envelopeKind` is the result of a 5-byte magic probe (`\"recipient\"` / `\"passphrase\"` / `\"none\"`). Operators administering a multi-strategy backup repository can now filter bundles by encryption posture or by format without a full restore cycle.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`listBundles()` carries inferred format per bundle",
|
|
13
|
-
"body": "Each entry now includes `format` alongside `bundleId` / `createdAt` / `size`. Inference is from the storage key suffix the writeBundle path produced — `<bid>/bundle.tar` → tar, `<bid>/bundle.tar.gz` → tar.gz, anything else → directory. Cheap: no byte read, no per-key stat call. Operators rendering a bundle picker UI now sort + filter by format from a single list call."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`storage.bundleInfo(bundleId)` — per-bundle introspection",
|
|
17
|
-
"body": "Returns `{ bundleId, format, envelopeKind, sizeBytes }`. `format` from the storage layout (no byte read). `envelopeKind` from `b.archive.sniffEnvelope` over the bundle payload — `\"recipient\"` (BAWRP / v0.12.10 hybrid PQC), `\"passphrase\"` (BAWPP / v0.12.11 Argon2id), `\"none\"` (plaintext or directory format). `sizeBytes` is the payload byte count for tar / tar.gz; null for directory format (operator's per-file walk applies if exact size matters). Nonexistent bundles refused with `backup/bundle-not-found`."
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.18",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`bundleAdapterStorage.listBundles({ withStats })` + `bundleInfo.createdAt` — opt-in mtime + size from `statKey`",
|
|
6
|
-
"summary": "Two additions on `b.backup.bundleAdapterStorage`. `listBundles({ withStats: true })` fans out `statKey` per bundle and populates `createdAt` (ISO string from mtimeMs) + `size` (bytes) on every entry. Without the opt the default fast path stays a single listKeys call — operators rendering a bundle picker UI choose between O(1) listings and O(N) stat-enriched listings explicitly. `bundleInfo(bundleId)` gains the same `createdAt` field for parity. fsAdapter + objectStoreAdapter both expose `statKey`; legacy adapters without the capability leave the fields null.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`storage.listBundles({ withStats: true })` — opt-in per-bundle stat fan-out",
|
|
13
|
-
"body": "When the adapter exposes `statKey`, populates `createdAt` (ISO string from mtimeMs) + `size` (bytes) per entry. Stat fan-out is O(N) round-trips so the opt is OFF by default — operators wanting cheap one-shot listings stay on `listBundles()`. Format precedence (tar.gz > tar > directory) carries through to which payload key gets stat'd."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`storage.bundleInfo(bundleId)` returns `createdAt`",
|
|
17
|
-
"body": "The bundle introspection primitive now returns `{ bundleId, format, envelopeKind, sizeBytes, createdAt }`. `createdAt` is the ISO string derived from `statKey.mtimeMs` (when the adapter exposes it) — null otherwise. Matches the listBundles+withStats shape so operators can use bundleInfo for single-bundle drill-downs without re-mapping field names."
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.19",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`bundleAdapterStorage.verifyBundle(bundleId)` — bundle integrity check without restore + `b.safeArchive.inspect` tar.gz support",
|
|
6
|
-
"summary": "`storage.verifyBundle(bundleId, opts?)` walks a bundle without restoring it: confirms the payload exists, the envelope (if any) decrypts under the supplied key, and the inner tar / tar.gz walker enumerates every entry without writing to disk. Returns `{ ok, format, envelopeKind, entryCount, errors }` — operators wanting periodic health-check of a backup repository call verifyBundle across `listBundles()` and aggregate `ok === false` results. Composes the bundle's known format directly through the unwrap → gunzip → tar pipeline (skips safeArchive's auto-sniff because the bundle's format is already known from bundleInfo). `b.safeArchive.inspect` separately gains `format: \"tar.gz\"` dispatch — operators with a known tar.gz payload now get entry enumeration without extracting.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`storage.verifyBundle(bundleId, opts?)` — integrity check without restore",
|
|
13
|
-
"body": "Composes bundleInfo for format/envelope detection + the unwrap → gunzip → tar walker chain directly. opts.recipient / opts.passphrase override the storage's configured keys (useful for verifying a bundle under a different key set than the storage was opened with — e.g. verifying that a key rotation candidate can still read a bundle). Wrong key / corrupted payload returns `ok: false` with a typed error code in the errors array rather than throwing. Directory format reports ok=true based on manifest existence + readability (the inspect walker doesn't apply)."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`b.safeArchive.inspect` accepts `format: \"tar.gz\"`",
|
|
17
|
-
"body": "Mirrors the v0.12.9 extract surface: operators handed a `.tar.gz` payload can now enumerate entries without extracting. Composes `b.archive.read.gz` + `.asTar()` internally so the same bomb caps apply (1 GiB output / 100× ratio default) to inspect calls."
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.2",
|
|
4
|
-
"date": "2026-05-22",
|
|
5
|
-
"headline": "Release-process docs point at `scripts/release.js` (the orchestrator shipped in v0.12.0)",
|
|
6
|
-
"summary": "`CONTRIBUTING.md` (maintainer section) and `examples/wiki/DEPLOY.md` (\"Tag-driven releases\") described the old multi-step manual release flow — version bump → commit → push → tag → push tag — without mentioning the v0.12.0 orchestrator. Both docs now point at `node scripts/release.js` as the canonical release mechanism, list the eight idempotent subcommands, and call out the two pre-requisites the script enforces (release-notes JSON + signed-commit config).",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Changed",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`CONTRIBUTING.md` maintainer section names the orchestrator",
|
|
13
|
-
"body": "The release-process bullet now reads `node scripts/release.js — eight idempotent subcommands (prepare → smoke → commit → push → watch → merge → tag → publish) plus all for a one-shot`. The existing DEPLOY.md link stays as a pointer for the wiki-container side of the same flow."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`examples/wiki/DEPLOY.md` Tag-driven releases section rewritten",
|
|
17
|
-
"body": "Replaces the four-bullet manual flow with the orchestrator surface, including the `all` / `all --minor` one-shot, the per-phase subcommands, and the pre-requisites the script enforces (release-notes JSON present, SSH signing config in place). The downstream wiki-image deploy step on the host (pin `docker-compose.prod.yml` + `docker compose pull && up -d`) is unchanged."
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"heading": "Fixed",
|
|
23
|
-
"items": [
|
|
24
|
-
{
|
|
25
|
-
"title": "`scripts/release.js` signature verification uses `git verify-commit` as the canonical truth",
|
|
26
|
-
"body": "The v0.12.0 orchestrator's commit-signature gate parsed `git log -1 --pretty=%h %G? %GS` looking for `G` in the second column. On some platforms the `%G?` format token's `?` character can be eaten by argument resolution, returning empty stdout even when the signature is Good. The fix runs `git verify-commit HEAD` (whose exit code is the canonical signal `required_signatures` GH ruleset enforces) as the primary check; the `%G?` parse stays as a human-readable confirmation but no longer gates the script. Surfaced via dogfooding the orchestrator on this very release."
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"title": "`scripts/release.js` Docker bind-mount path handles Windows host paths with spaces",
|
|
30
|
-
"body": "The `push` phase's gitleaks step bind-mounted the repo root via `-v <path>:/repo`. The previous path transform produced `/C:/Users/...` on Windows, which Docker's `-v src:dst[:mode]` parser interpreted as having three colon-separated fields. Fix: transform `C:\\Users\\...` to `//c/Users/...` (lowercased drive letter, double-slash prefix — matches Git Bash's `$(pwd)` form Docker Desktop accepts). POSIX hosts pass through unchanged. Operators with Windows paths containing spaces, parentheses, or special characters can now run `node scripts/release.js push` without manual mount fiddling."
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"heading": "Added",
|
|
36
|
-
"items": [
|
|
37
|
-
{
|
|
38
|
-
"title": "`scripts/release.js regen` — re-run artifact regeneration mid-flow",
|
|
39
|
-
"body": "Edits to `release-notes/v<next>.json` after `prepare` (e.g. addressing a Codex finding, fixing a leak-vocabulary refusal) previously required running `node scripts/generate-changelog-entry.js --rebuild` + `scripts/refresh-api-snapshot.js` + `scripts/check-api-snapshot.js` + `scripts/check-changelog-extract.js` manually. The new `regen` subcommand wraps all four into a single idempotent step. Safe to run any time from any branch. The `prepare` phase calls the same shared helper internally so behaviour stays consistent."
|
|
40
|
-
}
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
],
|
|
44
|
-
"references": []
|
|
45
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.20",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`bundleAdapterStorage.verifyAllBundles(opts)` — bounded-parallel batch integrity walk with stopOnFirstFailure short-circuit",
|
|
6
|
-
"summary": "Batch wrapper over the v0.12.19 verifyBundle primitive. `storage.verifyAllBundles(opts?)` iterates `listBundles()` + walks each bundle with a bounded-parallel pool. Returns `{ total, ok, failed, results }` where `results` carries every per-bundle verifyBundle output (including bundleId, format, envelopeKind, entryCount, errors). `opts.concurrency` defaults to 4 (gentle on the storage backend); `opts.stopOnFirstFailure` short-circuits the walk when an unhealthy bundle is found (default off — operators want the full health report). `opts.recipient` / `opts.passphrase` forwarded to every per-bundle verify call. Operators wiring a periodic cron job over a backup repository now have a single primitive to call instead of hand-rolling the listBundles loop.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`storage.verifyAllBundles(opts?)` — batch integrity walk",
|
|
13
|
-
"body": "Iterates `listBundles()` + calls `verifyBundle(bundleId, opts)` on each. Bounded-parallel fan-out (default 4 workers; `opts.concurrency` raises or lowers); each worker pulls from a shared queue + the warm-up keeps `concurrency` workers in flight until the queue drains. Returns the aggregate `{ total, ok, failed, results }` with `results` sorted by bundleId so the report is stable across runs regardless of completion order. `opts.stopOnFirstFailure` short-circuits the walk when the first unhealthy bundle is found — useful for fast-fail CI gates that don't need to enumerate every failure."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.21",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`bundleAdapterStorage.rewrapBundle(bundleId, opts)` — key rotation without restore + rewrite of inner archive bytes",
|
|
6
|
-
"summary": "`storage.rewrapBundle(bundleId, opts?)` rotates a bundle's wrap envelope under a new recipient keypair or passphrase WITHOUT touching the inner tar / tar.gz bytes. Operators rotating a compromised keypair, migrating to a new HSM, or refreshing passphrases on a HIPAA-posture repository previously had to `readBundle` → write to a stage dir → `writeBundle` under the new key — three byte-walks of the bundle payload, two filesystem touches, transient plaintext on disk. rewrapBundle does it as unwrap + rewrap in memory: zero disk plaintext, one round-trip through the wrap layer, the gzipped tar archive bytes inside the envelope are never inflated. Cross-kind rotation (recipient ↔ passphrase) is refused — that's a separate migration the operator configures with explicit cryptoStrategy switch.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`storage.rewrapBundle(bundleId, opts?)` — in-place envelope rotation",
|
|
13
|
-
"body": "Unwraps the bundle under the old key (storage's configured recipient/passphrase OR `opts.oldRecipient` / `opts.oldPassphrase`), re-wraps under the new key (`opts.newRecipient` / `opts.newPassphrase`), writes the rewrapped bytes back to the same storage key. Returns `{ bundleId, oldEnvelopeKind, newEnvelopeKind, bytesRewritten }`. Plaintext bundles refused with `backup/no-envelope-to-rewrap`; cross-kind rotation refused with `backup/no-new-recipient` / `backup/no-new-passphrase`. The inner archive bytes (the gz-compressed tar payload) are never decompressed or re-encoded — rewrap is a wrap-layer-only operation."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"heading": "Security",
|
|
19
|
-
"items": [
|
|
20
|
-
{
|
|
21
|
-
"title": "Zero plaintext on disk during rotation",
|
|
22
|
-
"body": "The inner tar bytes flow only through memory — old-envelope unwrap → new-envelope wrap → adapter writeFile. Operators previously rotating via readBundle + writeBundle wrote plaintext archive bytes to a temporary stage directory; rewrapBundle removes that exposure window entirely. Matches the operator-side discipline that backup payloads should never land on disk in plaintext form during steady-state operations."
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.22",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`bundleAdapterStorage.rewrapAllBundles(opts)` — bounded-parallel batch envelope rotation with mixed-storage skip semantics",
|
|
6
|
-
"summary": "Batch wrapper over the v0.12.21 rewrapBundle primitive. `storage.rewrapAllBundles(opts?)` iterates `listBundles()` + rotates each bundle's wrap envelope through a bounded-parallel pool (default 4 workers). Plaintext bundles + directory-format bundles get skipped cleanly (recorded as `status: \"skipped\"` with a `reason` field); rewrap failures get bucketed into `status: \"failed\"`. Operators completing a key-rotation event across an entire backup repository now have a single call that handles mixed-strategy storage correctly. `opts.newRecipient` / `opts.newPassphrase` / `opts.oldRecipient` / `opts.oldPassphrase` / `opts.concurrency` / `opts.stopOnFirstFailure` mirror the verifyAllBundles + rewrapBundle surface.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`storage.rewrapAllBundles(opts?)` — batch envelope rotation",
|
|
13
|
-
"body": "Iterates listBundles() + dispatches each bundle through rewrapBundle with the operator-supplied new key. Returns `{ total, rotated, skipped, failed, results }` where the per-bundle results carry `{ status: \"rotated\" | \"skipped\" | \"failed\", oldEnvelopeKind, newEnvelopeKind, reason }`. Bounded-parallel fan-out (default 4) keeps the storage backend under control; opts.stopOnFirstFailure short-circuits on the first rotation that throws an unexpected error (skips don't trip the short-circuit — they're expected for mixed-strategy storage). Plaintext + directory bundles skipped with `reason: \"format-not-wrappable\"` / `reason: \"no-envelope\"` rather than reported as failures."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.23",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`bundleAdapterStorage.cloneBundle(src, dst, opts?)` — same-storage byte-verbatim bundle clone for pre-rotation snapshots",
|
|
6
|
-
"summary": "`storage.cloneBundle(srcBundleId, dstBundleId, opts?)` copies a bundle's adapter payload (bundle.tar / bundle.tar.gz / every directory key) from src to dst WITHOUT touching the envelope or inner archive. Encrypted bundles are cloned byte-verbatim — the new bundleId carries the same envelope under the same recipient/passphrase. Operators preserving a known-good snapshot before a destructive operation (rewrap, key rotation, schema migration, manual operator-side editing) get a single-call atomic clone instead of a manual readBundle → writeBundle cycle (which would re-encode through the envelope and adapter contracts, breaking byte-identity).",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`storage.cloneBundle(src, dst, opts?)` — byte-verbatim payload clone",
|
|
13
|
-
"body": "Reads the source bundle's storage keys + writes them under the destination bundleId without invoking the wrap layer, gunzip path, or tar walker. Encrypted bundles produce byte-identical clones (a tar.gz wrap-recipient envelope cloned via cloneBundle has bit-for-bit equal bytes to the source). Returns `{ srcBundleId, dstBundleId, format, keysCopied, bytesCopied }`. `opts.overwrite` (default false) gates whether to refuse if dstBundleId already exists. Same-id clones refused upfront with `backup/clone-same-id`."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.24",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`bundleAdapterStorage.findBundles(predicate, opts?)` — predicate-based filtering over listBundles entries",
|
|
6
|
-
"summary": "Small helper that composes with listBundles for operators wanting to filter the bundle set without hand-rolling the walk. `storage.findBundles(predicate, opts?)` iterates listBundles + returns entries where `predicate(entry)` is truthy. Predicate sees the listBundles entry shape (`{ bundleId, format, createdAt, size }`); `opts.withStats: true` enables `createdAt` + `size` for predicates that need them. Common operator filters — by format (`b => b.format === \"tar.gz\"`), by age (`b => Date.parse(b.createdAt) < cutoff`), by size — now read as a single call.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`storage.findBundles(predicate, opts?)` — predicate-based bundle filter",
|
|
13
|
-
"body": "Operator-supplied predicate runs against every listBundles entry; matches accumulate into the returned array. `opts.withStats: true` is forwarded to listBundles so predicates relying on `createdAt` / `size` see populated values. Non-function predicate refused upfront with `backup/bad-arg`. Predicate throws bubble up to the caller (operators see their own filter errors, not swallowed). Stable ordering is whatever listBundles produces (reverse-chronological by bundleId)."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.25",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`b.ai.disclosure.applyAll(scenario)` — bundle Art. 50(1) / 50(3) / 50(4) disclosures for mixed-modality AI systems",
|
|
6
|
-
"summary": "Composes the three v0.12.12 disclosure primitives (chatbot / deepfake / emotion) into a single bundled emit. Operators running mixed-modality AI systems (e.g. a chatbot that also generates images, or an emotion-recognition system embedded in a chat flow) declare which Art. 50 obligations apply via `scenario.kinds` and the primitive fans out to the per-obligation emit calls in one pass. Shared opts (jurisdiction, language, audit, correlationId) propagate to every per-kind emission so the cross-walk + audit-chain entries stay correlated across the bundle.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.ai.disclosure.applyAll(scenario)` — multi-obligation bundled emit",
|
|
13
|
-
"body": "`scenario.kinds: [\"chatbot\", \"deepfake\", \"emotion\"]` (subset) selects which Art. 50 obligations to satisfy. Per-kind required fields (session for chatbot, content + contentType for deepfake) refused upfront when missing. Returns `{ disclosures: { chatbot?, deepfake?, emotion? } }` with each entry being the corresponding primitive's emission payload. Shared opts propagate: `scenario.jurisdiction` / `scenario.language` / `scenario.audit` / `scenario.correlationId` reach every per-kind call so a US-CA deployment serving chat + image gets both the AB-853 cross-walk AND the Art. 50(1) audit event under the same correlationId."
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.26",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`b.compliance` posture cascades — `eu-ai-act` + `ca-ab-853` + `cac-genai-label` POSTURE_DEFAULTS + backup encryption refusal",
|
|
6
|
-
"summary": "Three new posture cascades wired into `b.compliance.POSTURE_DEFAULTS` + `KNOWN_POSTURES` + `REGIME_MAP` so operators globally pinning the EU AI Act / California AB-853 / China CAC GenAI postures get the right floors automatically: backupEncryptionRequired:true, auditChainSignedRequired:true, tlsMinVersion:TLSv1.3, requireVacuumAfterErase:true. `b.backup.bundleAdapterStorage` extends the encryption-required posture list to include the three new postures so `cryptoStrategy: \"none\"` is refused upfront under any of them (parity with HIPAA + PCI-DSS, which the operator surface has carried since v0.12.10). The canonical `eu-ai-act` posture is the production name; the legacy `ai-act` short name stays in KNOWN_POSTURES for back-compat with operators who pinned it pre-v0.12.26.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`eu-ai-act` posture cascade — Regulation (EU) 2024/1689",
|
|
13
|
-
"body": "POSTURE_DEFAULTS entry: backupEncryptionRequired:true (Art. 12 logging + Art. 15 robustness/cybersecurity demand encryption-at-rest for high-risk system training logs), auditChainSignedRequired:true (Art. 12 + Art. 13 audit-chain integrity), tlsMinVersion:TLSv1.3, requireVacuumAfterErase:true (Art. 50(4) synthetic-content provenance — residual EXIF / metadata pointing at the generating model must be cleared on erase). REGIME_MAP entry under jurisdiction:\"EU\" domain:\"ai-governance\". KNOWN_POSTURES carries both `eu-ai-act` (canonical) and `ai-act` (legacy short name)."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`ca-ab-853` posture cascade — California AB-853 effective 2026",
|
|
17
|
-
"body": "Same encryption + audit floor as eu-ai-act; jurisdiction:\"US-CA\". Model-generated content watermarking + disclosure regime. Operators serving California traffic pin this posture for the AB-853 §22949.91 obligations the v0.12.12 deepfake primitive's crossWalk references."
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"title": "`cac-genai-label` posture cascade — China CAC GenAI Service Measures",
|
|
21
|
-
"body": "Synthetic-content labelling per Art. 12 + algorithm filing per Art. 4. Same backup encryption + signed audit chain floor. Operators serving Chinese traffic pin this posture so the bundleAdapterStorage refuses plaintext bundles and the disclosure primitive's `jurisdiction: \"cn\"` cross-walk produces the right legal-reference array."
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"title": "`bundleAdapterStorage` BACKUP_ENCRYPTION_REQUIRED_POSTURES extended",
|
|
25
|
-
"body": "`hipaa` + `pci-dss` (the v0.12.10 baseline) joined by the three AI postures. `cryptoStrategy: \"none\"` refused upfront under any of `eu-ai-act` / `ca-ab-853` / `cac-genai-label` with `backup/posture-requires-encryption`. Operators wiring backup storage in a regulated AI deployment now get the same posture-driven gate that the storage primitive has always applied to health + payment data."
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
-
"version": "0.12.27",
|
|
4
|
-
"date": "2026-05-24",
|
|
5
|
-
"headline": "`b.ai.quota` — per-tenant, per-model AI usage budgets with atomic consume-and-check",
|
|
6
|
-
"summary": "`b.ai.quota.create(opts)` builds an enforcer that caps AI inference usage per `(tenant, model, dimension, period)` and defends OWASP LLM Top 10 2025 LLM10 (Unbounded Consumption) — the class that includes denial-of-wallet, where an attacker drives a high volume of pay-per-use inferences until the bill itself is the attack. Meter by `tokens`, `requests`, `cost-usd`, or `compute-hours` over a calendar-aligned UTC window (`second` through `month`). `consume(tenant, model, amount)` is a single atomic check-and-charge: under the default `hard` enforcement it reserves the amount only if it fits under the ceiling, otherwise it refuses without charging — the limit test and the charge are one indivisible operation, so there is no charge-then-refund window for a concurrent call to observe. The in-memory counter is per-process; multi-node deployments supply an `opts.store` adapter whose `reserve` (an atomic conditional test-and-charge — a Redis Lua script, a SQL `UPDATE ... WHERE used + :amt <= :limit RETURNING used`) and `add` are atomic on the shared backend to enforce one aggregate ceiling across the cluster without false denials under contention. Limit resolution is most-specific-first: `perTenantModel` over `perTenant` over `perModel` over the default `limit`; tenant and model identifiers are percent-encoded into the counter key so a hostile tenant name cannot collide with another tenant's budget.",
|
|
7
|
-
"sections": [
|
|
8
|
-
{
|
|
9
|
-
"heading": "Added",
|
|
10
|
-
"items": [
|
|
11
|
-
{
|
|
12
|
-
"title": "`b.ai.quota.create(opts)` — per-tenant AI usage-budget enforcer",
|
|
13
|
-
"body": "Returns `{ consume, check, snapshot, reset }` scoped to one `dimension` (`tokens` / `requests` / `cost-usd` / `compute-hours`) and one `period` (`second` / `minute` / `hour` / `day` / `week` (Monday-aligned) / `month` (1st-of-month), all UTC-aligned). `consume(tenant, model, amount, opts?)` returns `{ used, limit, remaining, allowed, exceeded, windowStart, resetsAt, ... }`. `check(tenant, model)` is the read-only snapshot. Spin up one enforcer per dimension you meter — a monthly `cost-usd` budget and a per-minute `tokens` burst cap coexist as two `create()` calls sharing one store. Defends OWASP LLM10:2025 Unbounded Consumption / denial-of-wallet; maps to NIST AI RMF (AI 100-1) MANAGE 2.x and EU AI Act Art. 15 (robustness / resource-exhaustion resilience)."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"title": "`hard` / `soft` / `warn` enforcement",
|
|
17
|
-
"body": "`hard` (default) refuses the over-budget call and throws `aiQuota/exceeded` without charging — the rejected reservation is refunded so the counter is untouched. `soft` admits the charge but reports `allowed: false` so the caller decides whether to honor it. `warn` admits and allows (advisory), flagging `exceeded: true`. A per-call `consume(..., { enforcement })` override lets one endpoint soften the mode for a trusted internal caller without a second enforcer. Every over-budget event emits `ai/quota-exceeded` through the drop-silent audit chain (`ai/quota-applied` on success), tagged with the active cluster node id for attribution."
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"title": "Cross-node aggregate budgets via `opts.store`",
|
|
21
|
-
"body": "The default counter is in-memory (per-process). Supply `opts.store` exposing atomic `reserve` / `add` / `get` / `reset` (a Redis Lua script, a shared SQL row) and the ceiling is enforced on the cluster-wide aggregate. `hard` mode goes through `reserve`, an atomic conditional test-and-charge that adds the amount only if it fits — so a concurrent over-budget call cannot transiently inflate the counter and falsely deny a smaller call that should fit. Per-tenant and per-model limit overrides (`perTenant` / `perModel` / `perTenantModel`) are validated at config time so a malformed cap surfaces at boot, not as a silent fall-through to the default."
|
|
22
|
-
}
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
}
|