@blamejs/blamejs-shop 0.0.122 → 0.0.124
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 +4 -0
- package/README.md +4 -0
- package/SECURITY.md +2 -2
- package/lib/storefront.js +383 -2
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/README.md +1 -0
- package/lib/vendor/blamejs/SECURITY.md +1 -0
- package/lib/vendor/blamejs/api-snapshot.json +27 -2
- package/lib/vendor/blamejs/index.js +1 -0
- package/lib/vendor/blamejs/lib/ai-dp.js +539 -0
- package/lib/vendor/blamejs/lib/crypto.js +9 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.29.json +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-dp.test.js +167 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +14 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +21 -0
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 0 — b.ai.dp float-safe differential privacy. Snapping-
|
|
4
|
+
* mechanism Laplace (Mironov 2012) + discrete Gaussian (CKS20) with
|
|
5
|
+
* CSPRNG noise; per-scope ε/δ budgets with basic + Rényi-DP
|
|
6
|
+
* accounting. Statistical checks validate the noise distributions;
|
|
7
|
+
* the budget tests validate composition + exhaustion.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var b = require("../../index");
|
|
11
|
+
var helpers = require("../helpers");
|
|
12
|
+
var check = helpers.check;
|
|
13
|
+
|
|
14
|
+
// A budget large enough that the statistical loops never exhaust it.
|
|
15
|
+
function _bigBudget() {
|
|
16
|
+
return b.ai.dp.budget({ scope: "stat", epsilon: 1e12, delta: 0.9, accounting: "basic", audit: false });
|
|
17
|
+
}
|
|
18
|
+
function _mean(xs) { var s = 0; for (var i = 0; i < xs.length; i++) s += xs[i]; return s / xs.length; }
|
|
19
|
+
function _variance(xs, mean) { var s = 0; for (var i = 0; i < xs.length; i++) s += (xs[i] - mean) * (xs[i] - mean); return s / xs.length; }
|
|
20
|
+
|
|
21
|
+
async function testMechanismValidation() {
|
|
22
|
+
var cases = [
|
|
23
|
+
[{ type: "exponential", sensitivity: 1, epsilon: 1 }, "aiDp/bad-mechanism"],
|
|
24
|
+
[{ type: "laplace", sensitivity: 0, epsilon: 1, bound: 10 }, "aiDp/bad-sensitivity"],
|
|
25
|
+
[{ type: "laplace", sensitivity: 1, epsilon: 0, bound: 10 }, "aiDp/bad-epsilon"],
|
|
26
|
+
[{ type: "laplace", sensitivity: 1, epsilon: 1 }, "aiDp/bad-bound"],
|
|
27
|
+
[{ type: "gaussian", sensitivity: 1, epsilon: 0.5 }, "aiDp/bad-delta"],
|
|
28
|
+
[{ type: "gaussian", sensitivity: 1, epsilon: 0.5, delta: 1 }, "aiDp/bad-delta"],
|
|
29
|
+
[{ type: "gaussian", sensitivity: 1, epsilon: 2, delta: 1e-6 }, "aiDp/epsilon-too-large"],
|
|
30
|
+
];
|
|
31
|
+
var ok = true;
|
|
32
|
+
for (var i = 0; i < cases.length; i++) {
|
|
33
|
+
var caught = null;
|
|
34
|
+
try { b.ai.dp.mechanism(cases[i][0]); } catch (e) { caught = e; }
|
|
35
|
+
if (!caught || caught.code !== cases[i][1]) { ok = false; check("mechanism case " + i + " expected " + cases[i][1] + " got " + (caught && caught.code), false); }
|
|
36
|
+
}
|
|
37
|
+
check("mechanism: malformed configs throw the right codes", ok);
|
|
38
|
+
var lap = b.ai.dp.mechanism({ type: "laplace", sensitivity: 1, epsilon: 0.5, bound: 1000 });
|
|
39
|
+
check("mechanism: laplace scale = sensitivity/epsilon", lap.scale === 2 && lap.delta === 0);
|
|
40
|
+
var gss = b.ai.dp.mechanism({ type: "gaussian", sensitivity: 1, epsilon: 0.5, delta: 1e-6 });
|
|
41
|
+
check("mechanism: gaussian computes sigma", gss.sigma > 0 && gss.sigma2 > 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function testSnappingLaplaceDistribution() {
|
|
45
|
+
var bud = _bigBudget();
|
|
46
|
+
var m = b.ai.dp.mechanism({ type: "laplace", sensitivity: 1, epsilon: 0.5, bound: 1e6 }); // scale 2 → Λ 2
|
|
47
|
+
var N = 40000, xs = [], onGrid = true;
|
|
48
|
+
for (var i = 0; i < N; i++) {
|
|
49
|
+
var v = bud.consume(m, 100).value;
|
|
50
|
+
xs.push(v);
|
|
51
|
+
if (v % 2 !== 0) onGrid = false; // snapping grid Λ = 2
|
|
52
|
+
}
|
|
53
|
+
check("laplace: every output lands on the power-of-two snapping grid", onGrid);
|
|
54
|
+
var mean = _mean(xs);
|
|
55
|
+
check("laplace: mean ≈ true value (100)", Math.abs(mean - 100) < 0.3);
|
|
56
|
+
var variance = _variance(xs, mean);
|
|
57
|
+
check("laplace: variance ≈ 2·scale² (8)", variance > 6.5 && variance < 9.5);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function testLaplaceClamping() {
|
|
61
|
+
var bud = _bigBudget();
|
|
62
|
+
var m = b.ai.dp.mechanism({ type: "laplace", sensitivity: 1, epsilon: 1, bound: 10 });
|
|
63
|
+
var withinBound = true;
|
|
64
|
+
for (var i = 0; i < 5000; i++) {
|
|
65
|
+
var v = bud.consume(m, 1000000).value; // true value far outside the bound
|
|
66
|
+
if (v < -10 || v > 10) withinBound = false;
|
|
67
|
+
}
|
|
68
|
+
check("laplace: output is clamped to ±bound", withinBound);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function testDiscreteGaussianDistribution() {
|
|
72
|
+
var bud = _bigBudget();
|
|
73
|
+
var m = b.ai.dp.mechanism({ type: "gaussian", sensitivity: 1, epsilon: 0.5, delta: 1e-6 });
|
|
74
|
+
var N = 40000, xs = [], allInt = true;
|
|
75
|
+
for (var i = 0; i < N; i++) {
|
|
76
|
+
var v = bud.consume(m, 0).value;
|
|
77
|
+
xs.push(v);
|
|
78
|
+
if (!Number.isInteger(v)) allInt = false;
|
|
79
|
+
}
|
|
80
|
+
check("gaussian: discrete — every output is an integer", allInt);
|
|
81
|
+
var mean = _mean(xs);
|
|
82
|
+
check("gaussian: mean ≈ 0", Math.abs(mean) < 0.5);
|
|
83
|
+
var variance = _variance(xs, mean);
|
|
84
|
+
// Discrete Gaussian variance ≈ σ² for σ not tiny.
|
|
85
|
+
check("gaussian: variance ≈ σ²", variance > m.sigma2 * 0.85 && variance < m.sigma2 * 1.15);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function testBudgetBasicComposition() {
|
|
89
|
+
var bud = b.ai.dp.budget({ scope: "t", epsilon: 1.0, delta: 1e-5, accounting: "basic", audit: false });
|
|
90
|
+
var m = b.ai.dp.mechanism({ type: "laplace", sensitivity: 1, epsilon: 0.3, bound: 100 });
|
|
91
|
+
var r1 = bud.consume(m, 5);
|
|
92
|
+
check("budget: consume returns { value, cost, remaining }",
|
|
93
|
+
typeof r1.value === "number" && r1.cost.epsilon === 0.3 && Math.abs(r1.remaining.epsilon - 0.7) < 1e-9);
|
|
94
|
+
bud.consume(m, 5);
|
|
95
|
+
bud.consume(m, 5); // spent 0.9
|
|
96
|
+
var refused = null;
|
|
97
|
+
try { bud.consume(m, 5); } catch (e) { refused = e; } // 0.9 + 0.3 > 1.0
|
|
98
|
+
check("budget: basic composition refuses over-ε release", refused && refused.code === "aiDp/budget-exhausted");
|
|
99
|
+
check("budget: spent reflects three releases", Math.abs(bud.spent().epsilon - 0.9) < 1e-9);
|
|
100
|
+
bud.reset();
|
|
101
|
+
check("budget: reset clears spend", bud.spent().epsilon === 0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function testBudgetDeltaExhaustion() {
|
|
105
|
+
var bud = b.ai.dp.budget({ scope: "t", epsilon: 1e6, delta: 2.5e-6, accounting: "basic", audit: false });
|
|
106
|
+
var m = b.ai.dp.mechanism({ type: "gaussian", sensitivity: 1, epsilon: 0.1, delta: 1e-6 });
|
|
107
|
+
bud.consume(m, 0); bud.consume(m, 0); // δ spent 2e-6
|
|
108
|
+
var refused = null;
|
|
109
|
+
try { bud.consume(m, 0); } catch (e) { refused = e; } // 2e-6 + 1e-6 > 2.5e-6
|
|
110
|
+
check("budget: δ budget exhaustion refuses (not just ε)", refused && refused.code === "aiDp/budget-exhausted");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function testRdpTighterThanBasic() {
|
|
114
|
+
function spend(acc) {
|
|
115
|
+
var bud = b.ai.dp.budget({ scope: "s", epsilon: 1e9, delta: 1e-3, accounting: acc, audit: false });
|
|
116
|
+
var m = b.ai.dp.mechanism({ type: "gaussian", sensitivity: 1, epsilon: 0.3, delta: 1e-6 });
|
|
117
|
+
for (var k = 0; k < 12; k++) bud.consume(m, 0);
|
|
118
|
+
return bud.spent().epsilon;
|
|
119
|
+
}
|
|
120
|
+
var basicEps = spend("basic");
|
|
121
|
+
var rdpEps = spend("rdp");
|
|
122
|
+
check("rdp: 12× Gaussian basic sums to 3.6", Math.abs(basicEps - 3.6) < 1e-9);
|
|
123
|
+
check("rdp: Rényi accounting is strictly tighter than basic", rdpEps < basicEps);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function testBudgetValidation() {
|
|
127
|
+
var cases = [
|
|
128
|
+
[{ scope: "", epsilon: 1 }, "aiDp/bad-scope"],
|
|
129
|
+
[{ scope: "s", epsilon: 0 }, "aiDp/bad-epsilon"],
|
|
130
|
+
[{ scope: "s", epsilon: 1, delta: 1 }, "aiDp/bad-delta"],
|
|
131
|
+
[{ scope: "s", epsilon: 1, delta: -1 }, "aiDp/bad-delta"],
|
|
132
|
+
[{ scope: "s", epsilon: 1, accounting: "zcdp" }, "aiDp/bad-accounting"],
|
|
133
|
+
[{ scope: "s", epsilon: 1, delta: 0, accounting: "rdp" }, "aiDp/bad-accounting"],
|
|
134
|
+
];
|
|
135
|
+
var ok = true;
|
|
136
|
+
for (var i = 0; i < cases.length; i++) {
|
|
137
|
+
var caught = null;
|
|
138
|
+
try { b.ai.dp.budget(cases[i][0]); } catch (e) { caught = e; }
|
|
139
|
+
if (!caught || caught.code !== cases[i][1]) { ok = false; check("budget case " + i + " expected " + cases[i][1] + " got " + (caught && caught.code), false); }
|
|
140
|
+
}
|
|
141
|
+
check("budget: malformed configs throw the right codes", ok);
|
|
142
|
+
// A gaussian mechanism needs a scope delta > 0.
|
|
143
|
+
var bud = b.ai.dp.budget({ scope: "s", epsilon: 1, accounting: "basic", audit: false });
|
|
144
|
+
var refused = null;
|
|
145
|
+
try { bud.consume(b.ai.dp.mechanism({ type: "gaussian", sensitivity: 1, epsilon: 0.5, delta: 1e-6 }), 0); } catch (e) { refused = e; }
|
|
146
|
+
check("budget: gaussian into a δ=0 scope refused", refused && refused.code === "aiDp/bad-delta");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function run() {
|
|
150
|
+
await testMechanismValidation();
|
|
151
|
+
await testSnappingLaplaceDistribution();
|
|
152
|
+
await testLaplaceClamping();
|
|
153
|
+
await testDiscreteGaussianDistribution();
|
|
154
|
+
await testBudgetBasicComposition();
|
|
155
|
+
await testBudgetDeltaExhaustion();
|
|
156
|
+
await testRdpTighterThanBasic();
|
|
157
|
+
await testBudgetValidation();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = { run: run };
|
|
161
|
+
|
|
162
|
+
if (require.main === module) {
|
|
163
|
+
run().then(
|
|
164
|
+
function () { console.log("[ai-dp] OK — " + helpers.getChecks() + " checks passed"); },
|
|
165
|
+
function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -2200,10 +2200,11 @@ async function testNoDuplicateCodeBlocks() {
|
|
|
2200
2200
|
files: [
|
|
2201
2201
|
"lib/ai-quota.js:_emitAudit",
|
|
2202
2202
|
"lib/ai-capability.js:_emitAudit",
|
|
2203
|
+
"lib/ai-dp.js:_emitAudit",
|
|
2203
2204
|
"lib/cert.js:_emitAudit",
|
|
2204
2205
|
"lib/mail-send-deliver.js:_auditEmit",
|
|
2205
2206
|
],
|
|
2206
|
-
reason: "v0.12.27 + v0.12.28 — per-module drop-silent audit-emit helper (`try { audit().safeEmit({ action, outcome, metadata }); } catch (_e) {}`). Same family as the archive / http-client _emitAudit cluster (feedback_audit_safeEmit_per_module_emitAudit_shape): ai-quota.js emits ai/quota-applied + ai/quota-exceeded, ai-capability.js emits ai/capability-routed + ai/capability-no-candidate, cert.js emits certificate-lifecycle events, mail-send-deliver.js emits delivery events. Each carries a primitive-specific `action:` namespace + metadata fields; consolidating would force a shared audit import and lose the per-primitive namespace operators grep for in audit logs.",
|
|
2207
|
+
reason: "v0.12.27 + v0.12.28 + v0.12.29 — per-module drop-silent audit-emit helper (`try { audit().safeEmit({ action, outcome, metadata }); } catch (_e) {}`). Same family as the archive / http-client _emitAudit cluster (feedback_audit_safeEmit_per_module_emitAudit_shape): ai-quota.js emits ai/quota-applied + ai/quota-exceeded, ai-capability.js emits ai/capability-routed + ai/capability-no-candidate, ai-dp.js emits dp/budget-consumed + dp/budget-exhausted, cert.js emits certificate-lifecycle events, mail-send-deliver.js emits delivery events. Each carries a primitive-specific `action:` namespace + metadata fields; consolidating would force a shared audit import and lose the per-primitive namespace operators grep for in audit logs.",
|
|
2207
2208
|
},
|
|
2208
2209
|
{
|
|
2209
2210
|
mode: "family-subset",
|
|
@@ -2219,11 +2220,22 @@ async function testNoDuplicateCodeBlocks() {
|
|
|
2219
2220
|
mode: "family-subset",
|
|
2220
2221
|
files: [
|
|
2221
2222
|
"lib/ai-capability.js:create",
|
|
2223
|
+
"lib/ai-dp.js:budget",
|
|
2222
2224
|
"lib/cert.js:create",
|
|
2223
2225
|
"lib/mail-send-deliver.js:create",
|
|
2224
2226
|
"lib/auth/sd-jwt-vc-holder.js:create",
|
|
2225
2227
|
],
|
|
2226
|
-
reason: "v0.12.28 — factory-primitive opts-validation prelude (`validateOpts.requireObject + validateOpts(allowedKeys) + per-field typed-error throws + closure-captured return`). ai-capability.create validates a model-descriptor registry + builds a router closure; cert.create / mail-send-deliver.create / sd-jwt-vc-holder.create each validate a distinct spec's opts (X.509 cert issuance / RFC 5321 SMTP send / SD-JWT-VC holder store). Each throws a primitive-specific typed error (AiCapabilityError / CertError / MailSendError / SdJwtVcError); the shingle is the create()-factory validation idiom, not behaviour. Same family as the v0.10.16 factory-primitive validateOpts cluster.",
|
|
2228
|
+
reason: "v0.12.28 + v0.12.29 — factory-primitive opts-validation prelude (`validateOpts.requireObject + validateOpts(allowedKeys) + per-field typed-error throws + closure-captured return`). ai-capability.create validates a model-descriptor registry + builds a router closure; ai-dp.budget validates a per-scope ε/δ budget + builds an accountant closure; cert.create / mail-send-deliver.create / sd-jwt-vc-holder.create each validate a distinct spec's opts (X.509 cert issuance / RFC 5321 SMTP send / SD-JWT-VC holder store). Each throws a primitive-specific typed error (AiCapabilityError / AiDpError / CertError / MailSendError / SdJwtVcError); the shingle is the create()-factory validation idiom, not behaviour. Same family as the v0.10.16 factory-primitive validateOpts cluster.",
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
mode: "family-subset",
|
|
2232
|
+
files: [
|
|
2233
|
+
"lib/ai-dp.js:mechanism",
|
|
2234
|
+
"lib/dora.js:_validateReportInput",
|
|
2235
|
+
"lib/config.js:loadDbBacked",
|
|
2236
|
+
"lib/guard-snapshot-envelope.js:validate",
|
|
2237
|
+
],
|
|
2238
|
+
reason: "v0.12.29 — input-shape validation prelude (`validateOpts(allowedKeys) + chained typeof / range guards + typed-error throw`). ai-dp.mechanism validates a DP mechanism descriptor (type / sensitivity / epsilon / delta / bound); dora._validateReportInput validates a DORA Art. 17 incident report; config.loadDbBacked validates DB-backed config opts; guard-snapshot-envelope.validate validates a sealed snapshot envelope. Each enforces a distinct spec's field set with a primitive-specific typed error; the shingle is the validateOpts-then-guard idiom, not behaviour.",
|
|
2227
2239
|
},
|
|
2228
2240
|
{
|
|
2229
2241
|
mode: "family-subset",
|
|
@@ -62,10 +62,31 @@ function testInputValidation() {
|
|
|
62
62
|
check("max < min throws RangeError", threw);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function testGenerateBytesUniformity() {
|
|
66
|
+
// Regression: Node's SHAKE256 XOF is non-uniform at outputLength 1
|
|
67
|
+
// (byte values 0x00 / 0xff never occur, low bit skews to ~0.54).
|
|
68
|
+
// b.crypto.random draws >= 2 bytes and slices so a 1-byte request
|
|
69
|
+
// is still uniform — without this, every per-byte CSPRNG consumer
|
|
70
|
+
// (e.g. b.ai.dp noise sampling) would inherit the bias.
|
|
71
|
+
var N = 60000;
|
|
72
|
+
var sawZero = false, sawMax = false, lowBitOnes = 0;
|
|
73
|
+
for (var i = 0; i < N; i += 1) {
|
|
74
|
+
var byte = b.crypto.generateBytes(1)[0];
|
|
75
|
+
if (byte === 0) sawZero = true;
|
|
76
|
+
if (byte === 255) sawMax = true; // allow:raw-byte-literal — 0xff byte value, not a size
|
|
77
|
+
lowBitOnes += (byte & 1);
|
|
78
|
+
}
|
|
79
|
+
check("generateBytes(1) can emit 0x00", sawZero);
|
|
80
|
+
check("generateBytes(1) can emit 0xff", sawMax);
|
|
81
|
+
var lowBitFrac = lowBitOnes / N;
|
|
82
|
+
check("generateBytes(1) low bit is balanced (~0.5)", lowBitFrac > 0.47 && lowBitFrac < 0.53);
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
function run() {
|
|
66
86
|
testRangeContract();
|
|
67
87
|
testDispersion();
|
|
68
88
|
testInputValidation();
|
|
89
|
+
testGenerateBytesUniformity();
|
|
69
90
|
}
|
|
70
91
|
|
|
71
92
|
if (require.main === module) run();
|
package/package.json
CHANGED