@takk/bayesoutputgate 1.0.0
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 +92 -0
- package/LICENSE +190 -0
- package/NOTICE +45 -0
- package/README.md +403 -0
- package/SECURITY.md +98 -0
- package/SPEC.md +467 -0
- package/dist/adapter/index.cjs +411 -0
- package/dist/adapter/index.d.cts +29 -0
- package/dist/adapter/index.d.ts +29 -0
- package/dist/adapter/index.js +404 -0
- package/dist/audit/index.cjs +82 -0
- package/dist/audit/index.d.cts +40 -0
- package/dist/audit/index.d.ts +40 -0
- package/dist/audit/index.js +77 -0
- package/dist/bayesfactor/index.cjs +152 -0
- package/dist/bayesfactor/index.d.cts +15 -0
- package/dist/bayesfactor/index.d.ts +15 -0
- package/dist/bayesfactor/index.js +149 -0
- package/dist/beta/index.cjs +180 -0
- package/dist/beta/index.d.cts +45 -0
- package/dist/beta/index.d.ts +45 -0
- package/dist/beta/index.js +178 -0
- package/dist/calibration/index.cjs +339 -0
- package/dist/calibration/index.d.cts +53 -0
- package/dist/calibration/index.d.ts +53 -0
- package/dist/calibration/index.js +333 -0
- package/dist/cli/index.cjs +968 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +966 -0
- package/dist/dimensions/index.cjs +106 -0
- package/dist/dimensions/index.d.cts +33 -0
- package/dist/dimensions/index.d.ts +33 -0
- package/dist/dimensions/index.js +104 -0
- package/dist/edge/index.cjs +1141 -0
- package/dist/edge/index.d.cts +12 -0
- package/dist/edge/index.d.ts +12 -0
- package/dist/edge/index.js +1109 -0
- package/dist/gate/index.cjs +803 -0
- package/dist/gate/index.d.cts +77 -0
- package/dist/gate/index.d.ts +77 -0
- package/dist/gate/index.js +799 -0
- package/dist/hypothesis/index.cjs +268 -0
- package/dist/hypothesis/index.d.cts +38 -0
- package/dist/hypothesis/index.d.ts +38 -0
- package/dist/hypothesis/index.js +266 -0
- package/dist/index.cjs +1141 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +1109 -0
- package/dist/likelihood/index.cjs +137 -0
- package/dist/likelihood/index.d.cts +23 -0
- package/dist/likelihood/index.d.ts +23 -0
- package/dist/likelihood/index.js +132 -0
- package/dist/node/index.cjs +1282 -0
- package/dist/node/index.d.cts +24 -0
- package/dist/node/index.d.ts +24 -0
- package/dist/node/index.js +1246 -0
- package/dist/policy/index.cjs +88 -0
- package/dist/policy/index.d.cts +11 -0
- package/dist/policy/index.d.ts +11 -0
- package/dist/policy/index.js +85 -0
- package/dist/types-bMjn1j4e.d.cts +159 -0
- package/dist/types-bMjn1j4e.d.ts +159 -0
- package/package.json +142 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var BayesOutputGateError = class _BayesOutputGateError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "BayesOutputGateError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
Object.setPrototypeOf(this, _BayesOutputGateError.prototype);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
function invariant(condition, code, message) {
|
|
12
|
+
if (!condition) {
|
|
13
|
+
throw new BayesOutputGateError(code, message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/mathspecial.ts
|
|
18
|
+
var LN_SQRT_2PI = 0.9189385332046728;
|
|
19
|
+
var LANCZOS_G = 7;
|
|
20
|
+
var LANCZOS_COEFFICIENTS = [
|
|
21
|
+
0.9999999999998099,
|
|
22
|
+
676.5203681218851,
|
|
23
|
+
-1259.1392167224028,
|
|
24
|
+
771.3234287776531,
|
|
25
|
+
-176.6150291621406,
|
|
26
|
+
12.507343278686905,
|
|
27
|
+
-0.13857109526572012,
|
|
28
|
+
9984369578019572e-21,
|
|
29
|
+
15056327351493116e-23
|
|
30
|
+
];
|
|
31
|
+
function lgamma(x) {
|
|
32
|
+
if (!Number.isFinite(x) || x <= 0) {
|
|
33
|
+
throw new BayesOutputGateError(
|
|
34
|
+
"NUMERIC",
|
|
35
|
+
`lgamma requires a positive finite argument, got ${x}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const z = x - 1;
|
|
39
|
+
let acc = LANCZOS_COEFFICIENTS[0];
|
|
40
|
+
for (let i = 1; i < LANCZOS_COEFFICIENTS.length; i++) {
|
|
41
|
+
acc += LANCZOS_COEFFICIENTS[i] / (z + i);
|
|
42
|
+
}
|
|
43
|
+
const t = z + LANCZOS_G + 0.5;
|
|
44
|
+
return LN_SQRT_2PI + (z + 0.5) * Math.log(t) - t + Math.log(acc);
|
|
45
|
+
}
|
|
46
|
+
function lbeta(a, b) {
|
|
47
|
+
return lgamma(a) + lgamma(b) - lgamma(a + b);
|
|
48
|
+
}
|
|
49
|
+
function betaLogDensity(x, a, b) {
|
|
50
|
+
if (a <= 0 || b <= 0) {
|
|
51
|
+
throw new BayesOutputGateError(
|
|
52
|
+
"NUMERIC",
|
|
53
|
+
`Beta shape parameters must be positive, got a=${a}, b=${b}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (x < 0 || x > 1 || !Number.isFinite(x)) {
|
|
57
|
+
return Number.NEGATIVE_INFINITY;
|
|
58
|
+
}
|
|
59
|
+
if (x === 0) {
|
|
60
|
+
if (a < 1) return Number.POSITIVE_INFINITY;
|
|
61
|
+
if (a > 1) return Number.NEGATIVE_INFINITY;
|
|
62
|
+
return -lbeta(a, b) + (b - 1) * Math.log(1);
|
|
63
|
+
}
|
|
64
|
+
if (x === 1) {
|
|
65
|
+
if (b < 1) return Number.POSITIVE_INFINITY;
|
|
66
|
+
if (b > 1) return Number.NEGATIVE_INFINITY;
|
|
67
|
+
return -lbeta(a, b);
|
|
68
|
+
}
|
|
69
|
+
return (a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - lbeta(a, b);
|
|
70
|
+
}
|
|
71
|
+
function clamp(x, lo, hi) {
|
|
72
|
+
if (x < lo) return lo;
|
|
73
|
+
if (x > hi) return hi;
|
|
74
|
+
return x;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/bayesfactor/index.ts
|
|
78
|
+
var LN_3 = Math.log(3);
|
|
79
|
+
var LN_10 = Math.log(10);
|
|
80
|
+
var LN_100 = Math.log(100);
|
|
81
|
+
var DENSITY_EPSILON = 1e-6;
|
|
82
|
+
function jeffreysStrength(logBayesFactor) {
|
|
83
|
+
const x = logBayesFactor;
|
|
84
|
+
if (x >= LN_100) return "decisive-high";
|
|
85
|
+
if (x >= LN_10) return "strong-high";
|
|
86
|
+
if (x >= LN_3) return "substantial-high";
|
|
87
|
+
if (x > -LN_3) return "inconclusive";
|
|
88
|
+
if (x > -LN_10) return "substantial-low";
|
|
89
|
+
if (x > -LN_100) return "strong-low";
|
|
90
|
+
return "decisive-low";
|
|
91
|
+
}
|
|
92
|
+
function bayesFactor(scores, models) {
|
|
93
|
+
invariant(models.length > 0, "INVALID_CONFIG", "at least one dimension model is required");
|
|
94
|
+
const map = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const s of scores) {
|
|
96
|
+
invariant(
|
|
97
|
+
Number.isFinite(s.value) && s.value >= 0 && s.value <= 1,
|
|
98
|
+
"INVALID_SCORE",
|
|
99
|
+
`score for "${s.dimension}" must be a finite number in [0, 1], got ${s.value}`
|
|
100
|
+
);
|
|
101
|
+
invariant(
|
|
102
|
+
!map.has(s.dimension),
|
|
103
|
+
"INVALID_SCORE",
|
|
104
|
+
`duplicate score for dimension "${s.dimension}"`
|
|
105
|
+
);
|
|
106
|
+
map.set(s.dimension, s.value);
|
|
107
|
+
}
|
|
108
|
+
const contributions = [];
|
|
109
|
+
let logBayesFactor = 0;
|
|
110
|
+
for (const model of models) {
|
|
111
|
+
const value = map.get(model.dimension);
|
|
112
|
+
if (value === void 0) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
invariant(
|
|
116
|
+
Number.isFinite(model.weight) && model.weight >= 0,
|
|
117
|
+
"INVALID_CONFIG",
|
|
118
|
+
`weight for "${model.dimension}" must be a non-negative finite number, got ${model.weight}`
|
|
119
|
+
);
|
|
120
|
+
const x = clamp(value, DENSITY_EPSILON, 1 - DENSITY_EPSILON);
|
|
121
|
+
const logHigh = betaLogDensity(x, model.high.a, model.high.b);
|
|
122
|
+
const logLow = betaLogDensity(x, model.low.a, model.low.b);
|
|
123
|
+
const weighted = model.weight * (logHigh - logLow);
|
|
124
|
+
logBayesFactor += weighted;
|
|
125
|
+
contributions.push({
|
|
126
|
+
dimension: model.dimension,
|
|
127
|
+
score: value,
|
|
128
|
+
logBayesFactor: weighted,
|
|
129
|
+
weight: model.weight
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
invariant(
|
|
133
|
+
contributions.length > 0,
|
|
134
|
+
"INVALID_SCORE",
|
|
135
|
+
"no scored dimension matched a configured dimension model"
|
|
136
|
+
);
|
|
137
|
+
invariant(Number.isFinite(logBayesFactor), "NUMERIC", "combined log-Bayes-Factor is not finite");
|
|
138
|
+
const strength = jeffreysStrength(logBayesFactor);
|
|
139
|
+
const favored = strength.endsWith("high") ? "high" : strength.endsWith("low") ? "low" : "inconclusive";
|
|
140
|
+
return {
|
|
141
|
+
logBayesFactor,
|
|
142
|
+
bayesFactor: Math.exp(logBayesFactor),
|
|
143
|
+
favored,
|
|
144
|
+
strength,
|
|
145
|
+
contributions
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/policy/index.ts
|
|
150
|
+
function posteriorHighQuality(logBayesFactor, priorHighQuality) {
|
|
151
|
+
invariant(
|
|
152
|
+
priorHighQuality > 0 && priorHighQuality < 1,
|
|
153
|
+
"INVALID_CONFIG",
|
|
154
|
+
`priorHighQuality must be in (0, 1), got ${priorHighQuality}`
|
|
155
|
+
);
|
|
156
|
+
invariant(
|
|
157
|
+
Number.isFinite(logBayesFactor),
|
|
158
|
+
"NUMERIC",
|
|
159
|
+
`logBayesFactor must be finite, got ${logBayesFactor}`
|
|
160
|
+
);
|
|
161
|
+
const logPosteriorOdds = logBayesFactor + Math.log(priorHighQuality / (1 - priorHighQuality));
|
|
162
|
+
if (logPosteriorOdds >= 0) {
|
|
163
|
+
return 1 / (1 + Math.exp(-logPosteriorOdds));
|
|
164
|
+
}
|
|
165
|
+
const odds = Math.exp(logPosteriorOdds);
|
|
166
|
+
return odds / (1 + odds);
|
|
167
|
+
}
|
|
168
|
+
function decide(logBayesFactor, policy) {
|
|
169
|
+
if (policy.kind === "bayes-factor") {
|
|
170
|
+
invariant(
|
|
171
|
+
policy.passAbove >= 1,
|
|
172
|
+
"INVALID_CONFIG",
|
|
173
|
+
`passAbove must be >= 1, got ${policy.passAbove}`
|
|
174
|
+
);
|
|
175
|
+
invariant(
|
|
176
|
+
policy.failBelow > 0 && policy.failBelow <= 1,
|
|
177
|
+
"INVALID_CONFIG",
|
|
178
|
+
`failBelow must be in (0, 1], got ${policy.failBelow}`
|
|
179
|
+
);
|
|
180
|
+
invariant(
|
|
181
|
+
policy.passAbove > policy.failBelow,
|
|
182
|
+
"INVALID_CONFIG",
|
|
183
|
+
"passAbove must be greater than failBelow"
|
|
184
|
+
);
|
|
185
|
+
const bayesFactor2 = Math.exp(logBayesFactor);
|
|
186
|
+
const action2 = bayesFactor2 >= policy.passAbove ? "pass" : bayesFactor2 <= policy.failBelow ? "fail" : "escalate";
|
|
187
|
+
return { action: action2, rationale: "bayes-factor" };
|
|
188
|
+
}
|
|
189
|
+
invariant(
|
|
190
|
+
policy.lossFalsePass >= 0 && policy.lossFalseFail >= 0 && policy.escalationCost >= 0,
|
|
191
|
+
"INVALID_CONFIG",
|
|
192
|
+
"decision-theoretic losses must be non-negative"
|
|
193
|
+
);
|
|
194
|
+
invariant(
|
|
195
|
+
Number.isFinite(policy.lossFalsePass) && Number.isFinite(policy.lossFalseFail) && Number.isFinite(policy.escalationCost),
|
|
196
|
+
"INVALID_CONFIG",
|
|
197
|
+
"decision-theoretic losses must be finite"
|
|
198
|
+
);
|
|
199
|
+
const posterior = posteriorHighQuality(logBayesFactor, policy.priorHighQuality);
|
|
200
|
+
const expectedLoss = {
|
|
201
|
+
pass: (1 - posterior) * policy.lossFalsePass,
|
|
202
|
+
fail: posterior * policy.lossFalseFail,
|
|
203
|
+
escalate: policy.escalationCost
|
|
204
|
+
};
|
|
205
|
+
let action = "pass";
|
|
206
|
+
let best = expectedLoss.pass;
|
|
207
|
+
if (expectedLoss.fail < best) {
|
|
208
|
+
action = "fail";
|
|
209
|
+
best = expectedLoss.fail;
|
|
210
|
+
}
|
|
211
|
+
if (expectedLoss.escalate < best) {
|
|
212
|
+
action = "escalate";
|
|
213
|
+
}
|
|
214
|
+
return { action, rationale: "expected-loss", posteriorHighQuality: posterior, expectedLoss };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/gate/index.ts
|
|
218
|
+
function toDecision(factor, policy, guards) {
|
|
219
|
+
const base = {
|
|
220
|
+
action: policy.action,
|
|
221
|
+
bayesFactor: factor.bayesFactor,
|
|
222
|
+
logBayesFactor: factor.logBayesFactor,
|
|
223
|
+
strength: factor.strength,
|
|
224
|
+
rationale: policy.rationale,
|
|
225
|
+
contributions: factor.contributions,
|
|
226
|
+
...policy.posteriorHighQuality !== void 0 ? { posteriorHighQuality: policy.posteriorHighQuality } : {},
|
|
227
|
+
...policy.expectedLoss !== void 0 ? { expectedLoss: policy.expectedLoss } : {}
|
|
228
|
+
};
|
|
229
|
+
{
|
|
230
|
+
return base;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function evaluate(scores, models, policy, guards) {
|
|
234
|
+
const factor = bayesFactor(scores, models);
|
|
235
|
+
const decision = decide(factor.logBayesFactor, policy);
|
|
236
|
+
return toDecision(factor, decision);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/adapter/index.ts
|
|
240
|
+
var TOOL_NAME = "bayes_output_gate";
|
|
241
|
+
var DEFAULT_POLICY = { kind: "bayes-factor", passAbove: 10, failBelow: 0.1 };
|
|
242
|
+
function toJsonSafe(value) {
|
|
243
|
+
if (typeof value === "number") {
|
|
244
|
+
return Number.isFinite(value) ? value : null;
|
|
245
|
+
}
|
|
246
|
+
if (Array.isArray(value)) {
|
|
247
|
+
return value.map((item) => toJsonSafe(item));
|
|
248
|
+
}
|
|
249
|
+
if (value !== null && typeof value === "object") {
|
|
250
|
+
const out = {};
|
|
251
|
+
for (const [key, item] of Object.entries(value)) {
|
|
252
|
+
out[key] = toJsonSafe(item);
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
function asObject(value, message) {
|
|
259
|
+
invariant(
|
|
260
|
+
typeof value === "object" && value !== null && !Array.isArray(value),
|
|
261
|
+
"INVALID_CONFIG",
|
|
262
|
+
message
|
|
263
|
+
);
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
function parseBeta(value, where) {
|
|
267
|
+
const obj = asObject(value, `${where} must be an object with positive a and b`);
|
|
268
|
+
const a = obj["a"];
|
|
269
|
+
const b = obj["b"];
|
|
270
|
+
invariant(
|
|
271
|
+
typeof a === "number" && typeof b === "number" && a > 0 && b > 0,
|
|
272
|
+
"INVALID_CONFIG",
|
|
273
|
+
`${where} must have positive numeric a and b`
|
|
274
|
+
);
|
|
275
|
+
return { a, b };
|
|
276
|
+
}
|
|
277
|
+
function parseScore(value) {
|
|
278
|
+
const obj = asObject(value, "each score must be an object with dimension and value");
|
|
279
|
+
const dimension = obj["dimension"];
|
|
280
|
+
const score = obj["value"];
|
|
281
|
+
invariant(
|
|
282
|
+
typeof dimension === "string" && dimension.length > 0,
|
|
283
|
+
"INVALID_SCORE",
|
|
284
|
+
"score dimension must be a non-empty string"
|
|
285
|
+
);
|
|
286
|
+
invariant(
|
|
287
|
+
typeof score === "number" && Number.isFinite(score) && score >= 0 && score <= 1,
|
|
288
|
+
"INVALID_SCORE",
|
|
289
|
+
`score value for "${dimension}" must be a finite number in [0, 1]`
|
|
290
|
+
);
|
|
291
|
+
return { dimension, value: score };
|
|
292
|
+
}
|
|
293
|
+
function parseModel(value) {
|
|
294
|
+
const obj = asObject(value, "each model must be an object");
|
|
295
|
+
const dimension = obj["dimension"];
|
|
296
|
+
invariant(
|
|
297
|
+
typeof dimension === "string" && dimension.length > 0,
|
|
298
|
+
"INVALID_CONFIG",
|
|
299
|
+
"model dimension must be a non-empty string"
|
|
300
|
+
);
|
|
301
|
+
const rawWeight = obj["weight"];
|
|
302
|
+
const weight = rawWeight === void 0 ? 1 : rawWeight;
|
|
303
|
+
invariant(
|
|
304
|
+
typeof weight === "number" && Number.isFinite(weight) && weight >= 0,
|
|
305
|
+
"INVALID_CONFIG",
|
|
306
|
+
`model weight for "${dimension}" must be a non-negative number`
|
|
307
|
+
);
|
|
308
|
+
return {
|
|
309
|
+
dimension,
|
|
310
|
+
high: parseBeta(obj["high"], `model "${dimension}" high`),
|
|
311
|
+
low: parseBeta(obj["low"], `model "${dimension}" low`),
|
|
312
|
+
weight
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function parsePolicy(value) {
|
|
316
|
+
if (value === void 0) {
|
|
317
|
+
return DEFAULT_POLICY;
|
|
318
|
+
}
|
|
319
|
+
const obj = asObject(value, "policy must be an object");
|
|
320
|
+
const kind = obj["kind"];
|
|
321
|
+
if (kind === "decision-theoretic") {
|
|
322
|
+
const prior = obj["priorHighQuality"];
|
|
323
|
+
const lossFalsePass = obj["lossFalsePass"];
|
|
324
|
+
const lossFalseFail = obj["lossFalseFail"];
|
|
325
|
+
const escalationCost = obj["escalationCost"];
|
|
326
|
+
invariant(
|
|
327
|
+
typeof prior === "number" && typeof lossFalsePass === "number" && typeof lossFalseFail === "number" && typeof escalationCost === "number",
|
|
328
|
+
"INVALID_CONFIG",
|
|
329
|
+
"decision-theoretic policy requires numeric priorHighQuality, lossFalsePass, lossFalseFail, escalationCost"
|
|
330
|
+
);
|
|
331
|
+
return {
|
|
332
|
+
kind: "decision-theoretic",
|
|
333
|
+
priorHighQuality: prior,
|
|
334
|
+
lossFalsePass,
|
|
335
|
+
lossFalseFail,
|
|
336
|
+
escalationCost
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
invariant(kind === "bayes-factor", "INVALID_CONFIG", `unknown policy kind ${String(kind)}`);
|
|
340
|
+
const passAbove = obj["passAbove"];
|
|
341
|
+
const failBelow = obj["failBelow"];
|
|
342
|
+
invariant(
|
|
343
|
+
typeof passAbove === "number" && typeof failBelow === "number",
|
|
344
|
+
"INVALID_CONFIG",
|
|
345
|
+
"bayes-factor policy requires numeric passAbove and failBelow"
|
|
346
|
+
);
|
|
347
|
+
return { kind: "bayes-factor", passAbove, failBelow };
|
|
348
|
+
}
|
|
349
|
+
function parseGateInput(input) {
|
|
350
|
+
const obj = asObject(input, "tool input must be an object");
|
|
351
|
+
invariant(Array.isArray(obj["scores"]), "INVALID_SCORE", "scores must be an array");
|
|
352
|
+
invariant(Array.isArray(obj["models"]), "INVALID_CONFIG", "models must be an array");
|
|
353
|
+
const scores = obj["scores"].map(parseScore);
|
|
354
|
+
const models = obj["models"].map(parseModel);
|
|
355
|
+
invariant(models.length > 0, "INVALID_CONFIG", "at least one model is required");
|
|
356
|
+
return { scores, models, policy: parsePolicy(obj["policy"]) };
|
|
357
|
+
}
|
|
358
|
+
function runTool(input) {
|
|
359
|
+
const { scores, models, policy } = parseGateInput(input);
|
|
360
|
+
return evaluate(scores, models, policy);
|
|
361
|
+
}
|
|
362
|
+
function describeTool() {
|
|
363
|
+
return {
|
|
364
|
+
name: TOOL_NAME,
|
|
365
|
+
description: "Weigh an output's per-dimension quality scores against a high-quality and a low-quality hypothesis with a calibrated Bayes Factor, and return a pass, fail, or escalate decision on the Jeffreys scale.",
|
|
366
|
+
inputSchema: {
|
|
367
|
+
type: "object",
|
|
368
|
+
required: ["scores", "models"],
|
|
369
|
+
properties: {
|
|
370
|
+
scores: {
|
|
371
|
+
type: "array",
|
|
372
|
+
items: {
|
|
373
|
+
type: "object",
|
|
374
|
+
required: ["dimension", "value"],
|
|
375
|
+
properties: {
|
|
376
|
+
dimension: { type: "string" },
|
|
377
|
+
value: { type: "number", minimum: 0, maximum: 1 }
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
models: {
|
|
382
|
+
type: "array",
|
|
383
|
+
items: {
|
|
384
|
+
type: "object",
|
|
385
|
+
required: ["dimension", "high", "low"],
|
|
386
|
+
properties: {
|
|
387
|
+
dimension: { type: "string" },
|
|
388
|
+
high: { type: "object", required: ["a", "b"] },
|
|
389
|
+
low: { type: "object", required: ["a", "b"] },
|
|
390
|
+
weight: { type: "number", minimum: 0 }
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
policy: { type: "object" }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
var bayesOutputGateTool = {
|
|
400
|
+
...describeTool(),
|
|
401
|
+
handler: (input) => toJsonSafe(runTool(input))
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
export { TOOL_NAME, bayesOutputGateTool, describeTool, parseGateInput, runTool, toJsonSafe };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/canonical.ts
|
|
4
|
+
function canonicalize(value) {
|
|
5
|
+
return serialize(value);
|
|
6
|
+
}
|
|
7
|
+
function serialize(value) {
|
|
8
|
+
if (value === null) {
|
|
9
|
+
return "null";
|
|
10
|
+
}
|
|
11
|
+
const kind = typeof value;
|
|
12
|
+
if (kind === "number") {
|
|
13
|
+
return Number.isFinite(value) ? String(value) : "null";
|
|
14
|
+
}
|
|
15
|
+
if (kind === "boolean") {
|
|
16
|
+
return value ? "true" : "false";
|
|
17
|
+
}
|
|
18
|
+
if (kind === "string") {
|
|
19
|
+
return JSON.stringify(value);
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return `[${value.map((item) => serialize(item)).join(",")}]`;
|
|
23
|
+
}
|
|
24
|
+
if (kind === "object") {
|
|
25
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
26
|
+
const body = entries.map(([key, v]) => `${JSON.stringify(key)}:${serialize(v)}`).join(",");
|
|
27
|
+
return `{${body}}`;
|
|
28
|
+
}
|
|
29
|
+
return "null";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/audit/index.ts
|
|
33
|
+
var GENESIS_HASH = "0".repeat(64);
|
|
34
|
+
async function sha256Hex(input) {
|
|
35
|
+
const bytes = new TextEncoder().encode(input);
|
|
36
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
37
|
+
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
38
|
+
}
|
|
39
|
+
function recordOf(index, payload, previousHash, timestamp) {
|
|
40
|
+
return timestamp !== void 0 ? { index, timestamp, payload, previousHash } : { index, payload, previousHash };
|
|
41
|
+
}
|
|
42
|
+
var AuditChain = class {
|
|
43
|
+
entries = [];
|
|
44
|
+
/** Append a payload, sealing it against the previous entry's hash. */
|
|
45
|
+
async append(payload, meta = {}) {
|
|
46
|
+
const index = this.entries.length;
|
|
47
|
+
const previousHash = index === 0 ? GENESIS_HASH : this.entries[index - 1].hash;
|
|
48
|
+
const record = recordOf(index, payload, previousHash, meta.timestamp);
|
|
49
|
+
const hash = await sha256Hex(canonicalize(record));
|
|
50
|
+
const entry = meta.timestamp !== void 0 ? { index, timestamp: meta.timestamp, payload, previousHash, hash } : { index, payload, previousHash, hash };
|
|
51
|
+
this.entries.push(entry);
|
|
52
|
+
return entry;
|
|
53
|
+
}
|
|
54
|
+
/** A copy of the chain's entries in order. */
|
|
55
|
+
toArray() {
|
|
56
|
+
return [...this.entries];
|
|
57
|
+
}
|
|
58
|
+
/** Number of entries in the chain. */
|
|
59
|
+
get length() {
|
|
60
|
+
return this.entries.length;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
async function verifyChain(entries) {
|
|
64
|
+
for (let i = 0; i < entries.length; i++) {
|
|
65
|
+
const entry = entries[i];
|
|
66
|
+
const expectedPrevious = i === 0 ? GENESIS_HASH : entries[i - 1].hash;
|
|
67
|
+
if (entry.index !== i || entry.previousHash !== expectedPrevious) {
|
|
68
|
+
return { valid: false, brokenAt: i };
|
|
69
|
+
}
|
|
70
|
+
const record = recordOf(entry.index, entry.payload, entry.previousHash, entry.timestamp);
|
|
71
|
+
const recomputed = await sha256Hex(canonicalize(record));
|
|
72
|
+
if (recomputed !== entry.hash) {
|
|
73
|
+
return { valid: false, brokenAt: i };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { valid: true };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
exports.AuditChain = AuditChain;
|
|
80
|
+
exports.GENESIS_HASH = GENESIS_HASH;
|
|
81
|
+
exports.sha256Hex = sha256Hex;
|
|
82
|
+
exports.verifyChain = verifyChain;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** The hash that precedes the first entry of a chain. */
|
|
2
|
+
declare const GENESIS_HASH: string;
|
|
3
|
+
/** Compute the lowercase hexadecimal SHA-256 digest of a string via the Web Crypto API. */
|
|
4
|
+
declare function sha256Hex(input: string): Promise<string>;
|
|
5
|
+
/** One sealed record in an audit chain. */
|
|
6
|
+
interface AuditEntry {
|
|
7
|
+
readonly index: number;
|
|
8
|
+
readonly timestamp?: string;
|
|
9
|
+
readonly payload: unknown;
|
|
10
|
+
readonly previousHash: string;
|
|
11
|
+
readonly hash: string;
|
|
12
|
+
}
|
|
13
|
+
/** Metadata a caller may attach when appending. */
|
|
14
|
+
interface AuditMeta {
|
|
15
|
+
readonly timestamp?: string;
|
|
16
|
+
}
|
|
17
|
+
/** An append-only, hash-chained log of gate decisions. */
|
|
18
|
+
declare class AuditChain {
|
|
19
|
+
private readonly entries;
|
|
20
|
+
/** Append a payload, sealing it against the previous entry's hash. */
|
|
21
|
+
append(payload: unknown, meta?: AuditMeta): Promise<AuditEntry>;
|
|
22
|
+
/** A copy of the chain's entries in order. */
|
|
23
|
+
toArray(): AuditEntry[];
|
|
24
|
+
/** Number of entries in the chain. */
|
|
25
|
+
get length(): number;
|
|
26
|
+
}
|
|
27
|
+
/** The result of verifying an audit chain. */
|
|
28
|
+
interface ChainVerification {
|
|
29
|
+
readonly valid: boolean;
|
|
30
|
+
/** Index of the first broken entry, present when the chain is invalid. */
|
|
31
|
+
readonly brokenAt?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Verify that an audit chain is intact: every entry's index is sequential, every link points at the
|
|
35
|
+
* prior hash, and every hash recomputes from the entry's canonical content. Returns the index of the
|
|
36
|
+
* first tampered or broken entry when invalid.
|
|
37
|
+
*/
|
|
38
|
+
declare function verifyChain(entries: readonly AuditEntry[]): Promise<ChainVerification>;
|
|
39
|
+
|
|
40
|
+
export { AuditChain, type AuditEntry, type AuditMeta, type ChainVerification, GENESIS_HASH, sha256Hex, verifyChain };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** The hash that precedes the first entry of a chain. */
|
|
2
|
+
declare const GENESIS_HASH: string;
|
|
3
|
+
/** Compute the lowercase hexadecimal SHA-256 digest of a string via the Web Crypto API. */
|
|
4
|
+
declare function sha256Hex(input: string): Promise<string>;
|
|
5
|
+
/** One sealed record in an audit chain. */
|
|
6
|
+
interface AuditEntry {
|
|
7
|
+
readonly index: number;
|
|
8
|
+
readonly timestamp?: string;
|
|
9
|
+
readonly payload: unknown;
|
|
10
|
+
readonly previousHash: string;
|
|
11
|
+
readonly hash: string;
|
|
12
|
+
}
|
|
13
|
+
/** Metadata a caller may attach when appending. */
|
|
14
|
+
interface AuditMeta {
|
|
15
|
+
readonly timestamp?: string;
|
|
16
|
+
}
|
|
17
|
+
/** An append-only, hash-chained log of gate decisions. */
|
|
18
|
+
declare class AuditChain {
|
|
19
|
+
private readonly entries;
|
|
20
|
+
/** Append a payload, sealing it against the previous entry's hash. */
|
|
21
|
+
append(payload: unknown, meta?: AuditMeta): Promise<AuditEntry>;
|
|
22
|
+
/** A copy of the chain's entries in order. */
|
|
23
|
+
toArray(): AuditEntry[];
|
|
24
|
+
/** Number of entries in the chain. */
|
|
25
|
+
get length(): number;
|
|
26
|
+
}
|
|
27
|
+
/** The result of verifying an audit chain. */
|
|
28
|
+
interface ChainVerification {
|
|
29
|
+
readonly valid: boolean;
|
|
30
|
+
/** Index of the first broken entry, present when the chain is invalid. */
|
|
31
|
+
readonly brokenAt?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Verify that an audit chain is intact: every entry's index is sequential, every link points at the
|
|
35
|
+
* prior hash, and every hash recomputes from the entry's canonical content. Returns the index of the
|
|
36
|
+
* first tampered or broken entry when invalid.
|
|
37
|
+
*/
|
|
38
|
+
declare function verifyChain(entries: readonly AuditEntry[]): Promise<ChainVerification>;
|
|
39
|
+
|
|
40
|
+
export { AuditChain, type AuditEntry, type AuditMeta, type ChainVerification, GENESIS_HASH, sha256Hex, verifyChain };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/canonical.ts
|
|
2
|
+
function canonicalize(value) {
|
|
3
|
+
return serialize(value);
|
|
4
|
+
}
|
|
5
|
+
function serialize(value) {
|
|
6
|
+
if (value === null) {
|
|
7
|
+
return "null";
|
|
8
|
+
}
|
|
9
|
+
const kind = typeof value;
|
|
10
|
+
if (kind === "number") {
|
|
11
|
+
return Number.isFinite(value) ? String(value) : "null";
|
|
12
|
+
}
|
|
13
|
+
if (kind === "boolean") {
|
|
14
|
+
return value ? "true" : "false";
|
|
15
|
+
}
|
|
16
|
+
if (kind === "string") {
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return `[${value.map((item) => serialize(item)).join(",")}]`;
|
|
21
|
+
}
|
|
22
|
+
if (kind === "object") {
|
|
23
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
24
|
+
const body = entries.map(([key, v]) => `${JSON.stringify(key)}:${serialize(v)}`).join(",");
|
|
25
|
+
return `{${body}}`;
|
|
26
|
+
}
|
|
27
|
+
return "null";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/audit/index.ts
|
|
31
|
+
var GENESIS_HASH = "0".repeat(64);
|
|
32
|
+
async function sha256Hex(input) {
|
|
33
|
+
const bytes = new TextEncoder().encode(input);
|
|
34
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
35
|
+
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
36
|
+
}
|
|
37
|
+
function recordOf(index, payload, previousHash, timestamp) {
|
|
38
|
+
return timestamp !== void 0 ? { index, timestamp, payload, previousHash } : { index, payload, previousHash };
|
|
39
|
+
}
|
|
40
|
+
var AuditChain = class {
|
|
41
|
+
entries = [];
|
|
42
|
+
/** Append a payload, sealing it against the previous entry's hash. */
|
|
43
|
+
async append(payload, meta = {}) {
|
|
44
|
+
const index = this.entries.length;
|
|
45
|
+
const previousHash = index === 0 ? GENESIS_HASH : this.entries[index - 1].hash;
|
|
46
|
+
const record = recordOf(index, payload, previousHash, meta.timestamp);
|
|
47
|
+
const hash = await sha256Hex(canonicalize(record));
|
|
48
|
+
const entry = meta.timestamp !== void 0 ? { index, timestamp: meta.timestamp, payload, previousHash, hash } : { index, payload, previousHash, hash };
|
|
49
|
+
this.entries.push(entry);
|
|
50
|
+
return entry;
|
|
51
|
+
}
|
|
52
|
+
/** A copy of the chain's entries in order. */
|
|
53
|
+
toArray() {
|
|
54
|
+
return [...this.entries];
|
|
55
|
+
}
|
|
56
|
+
/** Number of entries in the chain. */
|
|
57
|
+
get length() {
|
|
58
|
+
return this.entries.length;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
async function verifyChain(entries) {
|
|
62
|
+
for (let i = 0; i < entries.length; i++) {
|
|
63
|
+
const entry = entries[i];
|
|
64
|
+
const expectedPrevious = i === 0 ? GENESIS_HASH : entries[i - 1].hash;
|
|
65
|
+
if (entry.index !== i || entry.previousHash !== expectedPrevious) {
|
|
66
|
+
return { valid: false, brokenAt: i };
|
|
67
|
+
}
|
|
68
|
+
const record = recordOf(entry.index, entry.payload, entry.previousHash, entry.timestamp);
|
|
69
|
+
const recomputed = await sha256Hex(canonicalize(record));
|
|
70
|
+
if (recomputed !== entry.hash) {
|
|
71
|
+
return { valid: false, brokenAt: i };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { valid: true };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { AuditChain, GENESIS_HASH, sha256Hex, verifyChain };
|