@konneal/engine 0.2.144 → 0.2.146
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/dist/{ask-A54GAMZR.js → ask-RIWVOI3D.js} +3 -3
- package/dist/{chunk-XBZIJMS7.js → chunk-ETLSWSZ7.js} +5 -1
- package/dist/{chunk-Y5T6V5CB.js → chunk-VISPFCQP.js} +6 -5
- package/dist/{chunk-363Q6FOL.js → chunk-VYEMFSU5.js} +89 -5
- package/dist/refusal.d.ts +6 -0
- package/dist/{search-VEGQBEFC.js → search-2PKZJMUV.js} +1 -1
- package/dist/{tools-5Y763UWT.js → tools-CWPQQTBQ.js} +1 -1
- package/dist/verdict.d.ts +23 -1
- package/dist/worker_public/src/index.js +5 -5
- package/dist/worker_public/src/refusal.js +5 -3
- package/docs/API.md +5 -1
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +3 -3
- package/workers/worker_public/src/refusal.ts +9 -0
- package/workers/worker_public/src/verdict.ts +138 -9
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleAsk
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VISPFCQP.js";
|
|
4
4
|
import "./chunk-ECOYO7EX.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-VYEMFSU5.js";
|
|
6
6
|
import "./chunk-XH5QGX2O.js";
|
|
7
7
|
import "./chunk-NI4CIK6W.js";
|
|
8
8
|
import "./chunk-W7J5SFXJ.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-ETLSWSZ7.js";
|
|
10
10
|
import "./chunk-4NXH5ZI6.js";
|
|
11
11
|
import "./chunk-VT7DR6NQ.js";
|
|
12
12
|
import "./chunk-3FYJM7LH.js";
|
|
@@ -101,10 +101,14 @@ function cacheableAnswer(answer, scope) {
|
|
|
101
101
|
if (answer.includes(refusalAnswer())) return false;
|
|
102
102
|
return canonicalRefusal(answer, scope) === answer;
|
|
103
103
|
}
|
|
104
|
+
function refusalShaped(answer, scope) {
|
|
105
|
+
return !cacheableAnswer(answer, scope);
|
|
106
|
+
}
|
|
104
107
|
|
|
105
108
|
export {
|
|
106
109
|
refusalAnswer,
|
|
107
110
|
canonicalRefusal,
|
|
108
111
|
exciseMisplacedPin,
|
|
109
|
-
cacheableAnswer
|
|
112
|
+
cacheableAnswer,
|
|
113
|
+
refusalShaped
|
|
110
114
|
};
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
tableRetyped,
|
|
33
33
|
toolNote,
|
|
34
34
|
verdictNote
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-VYEMFSU5.js";
|
|
36
36
|
import {
|
|
37
37
|
NO_CONTEXT,
|
|
38
38
|
STAGE_NAMES,
|
|
@@ -91,8 +91,9 @@ import {
|
|
|
91
91
|
cacheableAnswer,
|
|
92
92
|
canonicalRefusal,
|
|
93
93
|
exciseMisplacedPin,
|
|
94
|
-
refusalAnswer
|
|
95
|
-
|
|
94
|
+
refusalAnswer,
|
|
95
|
+
refusalShaped
|
|
96
|
+
} from "./chunk-ETLSWSZ7.js";
|
|
96
97
|
import {
|
|
97
98
|
entitlementScope,
|
|
98
99
|
requestSalt,
|
|
@@ -2640,7 +2641,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2640
2641
|
type: "done",
|
|
2641
2642
|
model,
|
|
2642
2643
|
query_hash: queryHash,
|
|
2643
|
-
follow_ups: understanding?.follow_ups ?? [],
|
|
2644
|
+
follow_ups: refusalShaped(canonical0, refusalScope(answerCites(full))) ? [] : understanding?.follow_ups ?? [],
|
|
2644
2645
|
blocks: [...c2.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...aggregationBlock ? [aggregationBlock] : []],
|
|
2645
2646
|
context_applied: ctxApplied,
|
|
2646
2647
|
read: readAs(),
|
|
@@ -2822,7 +2823,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2822
2823
|
if (v.support !== "supported") console.log("entailment:", v.support, raced.score, raced.ungrounded_claims?.slice(0, 2));
|
|
2823
2824
|
}
|
|
2824
2825
|
}
|
|
2825
|
-
const out = { answer, citations: finalCites, ...jsonQuality ? { source_quality: jsonQuality, confidence_note: entailmentNote || qualityNote(jsonQuality), ...entailment ? { entailment } : {}, ...jsonQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(finalCites) } : {} } : {}, model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...aggregationBlock ? [aggregationBlock] : [], ...registerBlock ? [registerBlock] : [], ...completionBlocks], context_applied: ctxApplied, ...liveRecords ? { records: liveRecords } : {}, ...ablate ? { ablate } : {}, route: { route: routed.route, features: routed.features } };
|
|
2826
|
+
const out = { answer, citations: finalCites, ...jsonQuality ? { source_quality: jsonQuality, confidence_note: entailmentNote || qualityNote(jsonQuality), ...entailment ? { entailment } : {}, ...jsonQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(finalCites) } : {} } : {}, model: MODELS.member, query_hash: queryHash, follow_ups: refusalShaped(answer, refusalScope(answerCites(answer))) ? [] : understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...aggregationBlock ? [aggregationBlock] : [], ...registerBlock ? [registerBlock] : [], ...completionBlocks], context_applied: ctxApplied, ...liveRecords ? { records: liveRecords } : {}, ...ablate ? { ablate } : {}, route: { route: routed.route, features: routed.features } };
|
|
2826
2827
|
const cacheable = !contextual && !declaredCtx && !ablate && cacheableAnswer(answer, refusalScope(answerCites(answer))) && finalAnchors.violations.length === 0;
|
|
2827
2828
|
if (cacheable) {
|
|
2828
2829
|
const ck = exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt)));
|
|
@@ -270,14 +270,60 @@ function parseNumber(raw) {
|
|
|
270
270
|
s = s.replace(/\.(\d{3})$/, "$1");
|
|
271
271
|
return Number(s.replace(/,(?=\d{3}\b)/g, ""));
|
|
272
272
|
}
|
|
273
|
+
var TOL_SEP_SRC = "\\u00b1|\\bpm\\b|\\+/-";
|
|
274
|
+
var NUM_SRC = "[0-9][0-9 ,.]*[0-9]";
|
|
275
|
+
var UNIT_SRC = "%|\\u00b0?[A-Za-z\\u00b5\\u03a9]{0,6}";
|
|
276
|
+
function statedUncertainty(value, tolRaw, tolUnitRaw, valueUnit) {
|
|
277
|
+
const tolNum = parseNumber(tolRaw);
|
|
278
|
+
const tolUnit = (tolUnitRaw ?? "").trim();
|
|
279
|
+
const relative = tolUnit === "%";
|
|
280
|
+
return {
|
|
281
|
+
tol: relative ? Math.abs(value) * tolNum / 100 : tolNum,
|
|
282
|
+
stated: relative ? `\xB1 ${tolRaw} %` : `\xB1 ${tolRaw}${tolUnit ? ` ${tolUnit}` : ""}`,
|
|
283
|
+
relative,
|
|
284
|
+
unit: relative ? valueUnit : tolUnit || valueUnit
|
|
285
|
+
};
|
|
286
|
+
}
|
|
273
287
|
function extractParamsWithUnits(query, symbols) {
|
|
274
288
|
const values = {};
|
|
275
289
|
const units = {};
|
|
290
|
+
const uncertainties = {};
|
|
276
291
|
for (const sym of symbols) {
|
|
277
292
|
const leaf = sym.split(".").pop() ?? sym;
|
|
278
293
|
const esc = leaf.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
279
|
-
const
|
|
280
|
-
|
|
294
|
+
const reTolFirst = new RegExp(
|
|
295
|
+
`\\b${esc}\\b\\D{0,14}?(${NUM_SRC})\\s*(?:${TOL_SEP_SRC})\\s*(${NUM_SRC})\\s*(${UNIT_SRC})?`,
|
|
296
|
+
"iu"
|
|
297
|
+
);
|
|
298
|
+
const reTolAfter = new RegExp(
|
|
299
|
+
`\\b${esc}\\b\\D{0,14}?(${NUM_SRC})\\s*(${UNIT_SRC})\\s*(?:${TOL_SEP_SRC})\\s*(${NUM_SRC})\\s*(${UNIT_SRC})?`,
|
|
300
|
+
"iu"
|
|
301
|
+
);
|
|
302
|
+
const rePlain = new RegExp(
|
|
303
|
+
`\\b${esc}\\b\\D{0,14}?(${NUM_SRC})\\s*(${UNIT_SRC})?`,
|
|
304
|
+
"iu"
|
|
305
|
+
);
|
|
306
|
+
const mFirst = query.match(reTolFirst);
|
|
307
|
+
const mAfter = mFirst ? null : query.match(reTolAfter);
|
|
308
|
+
const mTol = mFirst ?? mAfter;
|
|
309
|
+
if (mTol) {
|
|
310
|
+
const value = parseNumber(mTol[1]);
|
|
311
|
+
if (!Number.isFinite(value)) continue;
|
|
312
|
+
values[sym] = value;
|
|
313
|
+
let unit;
|
|
314
|
+
let u;
|
|
315
|
+
if (mFirst) {
|
|
316
|
+
unit = (mFirst[3] ?? "").trim() || void 0;
|
|
317
|
+
u = statedUncertainty(value, mFirst[2], mFirst[3], unit);
|
|
318
|
+
} else {
|
|
319
|
+
unit = (mAfter[2] ?? "").trim() || void 0;
|
|
320
|
+
u = statedUncertainty(value, mAfter[3], mAfter[4], unit || void 0);
|
|
321
|
+
}
|
|
322
|
+
if (unit) units[sym] = unit;
|
|
323
|
+
uncertainties[sym] = u;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const m = query.match(rePlain);
|
|
281
327
|
if (m) {
|
|
282
328
|
const v = parseNumber(m[1]);
|
|
283
329
|
if (Number.isFinite(v)) {
|
|
@@ -287,7 +333,7 @@ function extractParamsWithUnits(query, symbols) {
|
|
|
287
333
|
}
|
|
288
334
|
}
|
|
289
335
|
}
|
|
290
|
-
return { values, units };
|
|
336
|
+
return { values, units, uncertainties };
|
|
291
337
|
}
|
|
292
338
|
function dimensionMismatch(check, units, dimensions) {
|
|
293
339
|
const dims = /* @__PURE__ */ new Map();
|
|
@@ -313,11 +359,14 @@ function evaluate(content, query, opts) {
|
|
|
313
359
|
let voidReason = null;
|
|
314
360
|
const machine = checks.map((expression) => {
|
|
315
361
|
const values = {};
|
|
362
|
+
const ids = [];
|
|
316
363
|
try {
|
|
317
|
-
for (const t of tokenize(expression)) if (t.t === "id"
|
|
364
|
+
for (const t of tokenize(expression)) if (t.t === "id") ids.push(t.v);
|
|
318
365
|
} catch {
|
|
319
366
|
}
|
|
367
|
+
for (const t of new Set(ids)) if (params[t] !== void 0) values[t] = params[t];
|
|
320
368
|
let result = null;
|
|
369
|
+
let uncertainty;
|
|
321
370
|
if (missing.length === 0) {
|
|
322
371
|
const mismatch = opts?.dimensions ? dimensionMismatch(expression, bound.units, opts.dimensions) : null;
|
|
323
372
|
if (mismatch) {
|
|
@@ -328,9 +377,38 @@ function evaluate(content, query, opts) {
|
|
|
328
377
|
} catch {
|
|
329
378
|
result = null;
|
|
330
379
|
}
|
|
380
|
+
const bandSyms = [...new Set(ids)].filter((s) => bound.uncertainties[s] !== void 0).slice(0, 3);
|
|
381
|
+
if (bandSyms.length && result !== null) {
|
|
382
|
+
const corners = [];
|
|
383
|
+
for (let mask = 0; mask < 1 << bandSyms.length; mask++) {
|
|
384
|
+
const corner = { ...params };
|
|
385
|
+
bandSyms.forEach((s, i) => {
|
|
386
|
+
corner[s] = corner[s] + (mask >> i & 1 ? 1 : -1) * bound.uncertainties[s].tol;
|
|
387
|
+
});
|
|
388
|
+
try {
|
|
389
|
+
corners.push(!!parseAndEval(expression, corner));
|
|
390
|
+
} catch {
|
|
391
|
+
corners.push(null);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const first = bandSyms[0];
|
|
395
|
+
const un = bound.uncertainties[first];
|
|
396
|
+
uncertainty = {
|
|
397
|
+
symbol: first,
|
|
398
|
+
stated: un.stated,
|
|
399
|
+
tol: un.tol,
|
|
400
|
+
unit: un.unit,
|
|
401
|
+
nominal: result,
|
|
402
|
+
corners
|
|
403
|
+
};
|
|
404
|
+
const decided = corners.filter((c2) => c2 !== null);
|
|
405
|
+
if (decided.length && decided.some((c2) => c2 !== result)) {
|
|
406
|
+
voidReason = voidReason ?? `the stated uncertainty (${un.stated}) on ${first.split(".").pop()} crosses the decision boundary \u2014 the check evaluates ${result ? "true" : "false"} at the stated value but ${decided.find((c2) => c2 !== result) ? "true" : "false"} at the band edge; the question cannot be decided as stated`;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
331
409
|
}
|
|
332
410
|
}
|
|
333
|
-
return { expression, symbolic: expression, values, result };
|
|
411
|
+
return { expression, symbolic: expression, values, result, uncertainty };
|
|
334
412
|
});
|
|
335
413
|
if (missing.length) {
|
|
336
414
|
return { verdict: "void", missing, checks: machine };
|
|
@@ -341,6 +419,7 @@ function evaluate(content, query, opts) {
|
|
|
341
419
|
if (voidReason) return { verdict: "void", missing: [], void_reason: voidReason, checks: machine };
|
|
342
420
|
return null;
|
|
343
421
|
}
|
|
422
|
+
if (voidReason) return { verdict: "void", missing: [], void_reason: voidReason, checks: machine };
|
|
344
423
|
return {
|
|
345
424
|
verdict: failed ? "fail" : "pass",
|
|
346
425
|
on_violation: failed ? String(c.on_violation ?? "invalid") : void 0,
|
|
@@ -356,6 +435,11 @@ function verdictNote(v, node) {
|
|
|
356
435
|
for (const c of v.checks) {
|
|
357
436
|
const vals = Object.entries(c.values).map(([k, n]) => `${k}=${n}`).join(", ");
|
|
358
437
|
lines.push(`- ${c.expression}${vals ? ` [${vals}]` : ""} \u2192 ${c.result === null ? "not evaluated" : c.result ? "holds" : "VIOLATED"}`);
|
|
438
|
+
if (c.uncertainty) {
|
|
439
|
+
const u = c.uncertainty;
|
|
440
|
+
const edges = u.corners.map((r) => r === null ? "not evaluated" : r ? "holds" : "VIOLATED").join("; ");
|
|
441
|
+
lines.push(` band ${u.stated}${u.unit && !u.stated.includes(u.unit) ? ` ${u.unit}` : ""} on ${u.symbol}: ${edges} across the band`);
|
|
442
|
+
}
|
|
359
443
|
}
|
|
360
444
|
if (v.verdict === "void") {
|
|
361
445
|
lines.push(v.void_reason ? `VERDICT: VOID \u2014 ${v.void_reason}. State the mismatch plainly; the question cannot be decided as stated.` : `VERDICT: VOID \u2014 the question does not state: ${v.missing.join(", ")}. Say exactly what is missing; never assume values.`);
|
package/dist/refusal.d.ts
CHANGED
|
@@ -40,3 +40,9 @@ export declare function exciseMisplacedPin(answer: string, cites: boolean): stri
|
|
|
40
40
|
* every later ask). An answer is cacheable only when it carries no
|
|
41
41
|
* pin AND the wall would not rewrite it. */
|
|
42
42
|
export declare function cacheableAnswer(answer: string, scope?: RefusalScope): boolean;
|
|
43
|
+
/** The follow-up-chips law (2026-10-13): the understanding stage's
|
|
44
|
+
* follow_ups are generated BEFORE retrieval and answer — after a
|
|
45
|
+
* refusal they advertise the very topic the corpus does not carry.
|
|
46
|
+
* Chips serve only when the answer is not refusal-shaped (carries no
|
|
47
|
+
* pin and the wall would not rewrite it). */
|
|
48
|
+
export declare function refusalShaped(answer: string, scope?: RefusalScope): boolean;
|
package/dist/verdict.d.ts
CHANGED
|
@@ -3,6 +3,16 @@ export interface MachineCheck {
|
|
|
3
3
|
symbolic: string;
|
|
4
4
|
values: Record<string, number>;
|
|
5
5
|
result: boolean | null;
|
|
6
|
+
/** present when the question states a tolerance on a symbol this
|
|
7
|
+
* check reads: the band's corner evaluations beside the nominal */
|
|
8
|
+
uncertainty?: {
|
|
9
|
+
symbol: string;
|
|
10
|
+
stated: string;
|
|
11
|
+
tol: number;
|
|
12
|
+
unit?: string;
|
|
13
|
+
nominal: boolean;
|
|
14
|
+
corners: (boolean | null)[];
|
|
15
|
+
};
|
|
6
16
|
}
|
|
7
17
|
export interface Verdict {
|
|
8
18
|
verdict: "pass" | "fail" | "void";
|
|
@@ -17,12 +27,24 @@ export interface Verdict {
|
|
|
17
27
|
export declare function extractChecks(content: unknown): string[];
|
|
18
28
|
export declare function symbolsIn(checks: string[]): string[];
|
|
19
29
|
export declare function extractParams(query: string, symbols: string[]): Record<string, number>;
|
|
30
|
+
/** A stated uncertainty: "± 60 v" / "pm 60 v" / "± 0.2 %". `tol` is
|
|
31
|
+
* absolute (a relative statement resolves against the stated value);
|
|
32
|
+
* `stated` quotes the source form for narration. */
|
|
33
|
+
export interface StatedUncertainty {
|
|
34
|
+
tol: number;
|
|
35
|
+
stated: string;
|
|
36
|
+
relative: boolean;
|
|
37
|
+
unit?: string;
|
|
38
|
+
}
|
|
20
39
|
/** The stated values WITH their unit tokens (TODO.sota/04's unit
|
|
21
40
|
* semantics): "E_max 30000 v" binds e_max=30000 and the unit "v" —
|
|
22
|
-
* the dimension check needs both.
|
|
41
|
+
* the dimension check needs both. A tolerance after the value —
|
|
42
|
+
* "30000 ± 60 v", "30000 v ± 60", "± 0.2 %", or the AsciiMath
|
|
43
|
+
* spelling "30000 pm 60 v" — binds a first-class uncertainty. */
|
|
23
44
|
export declare function extractParamsWithUnits(query: string, symbols: string[]): {
|
|
24
45
|
values: Record<string, number>;
|
|
25
46
|
units: Record<string, string>;
|
|
47
|
+
uncertainties: Record<string, StatedUncertainty>;
|
|
26
48
|
};
|
|
27
49
|
/** Dimension coherence (TODO.sota/04 item 2): the units plane gives
|
|
28
50
|
* each unit token a dimension; a parameter set whose bound symbols
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
handleMemories,
|
|
5
5
|
scoreFaithfulness,
|
|
6
6
|
scoreJudge
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-VISPFCQP.js";
|
|
8
8
|
import "../../chunk-ECOYO7EX.js";
|
|
9
9
|
import {
|
|
10
10
|
TOOLS_REGISTRY,
|
|
11
11
|
bindModelNode,
|
|
12
12
|
modelGroundingBlock
|
|
13
|
-
} from "../../chunk-
|
|
13
|
+
} from "../../chunk-VYEMFSU5.js";
|
|
14
14
|
import {
|
|
15
15
|
handleSearch
|
|
16
16
|
} from "../../chunk-5WITTJQD.js";
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
} from "../../chunk-W7J5SFXJ.js";
|
|
54
54
|
import {
|
|
55
55
|
canonicalRefusal
|
|
56
|
-
} from "../../chunk-
|
|
56
|
+
} from "../../chunk-ETLSWSZ7.js";
|
|
57
57
|
import {
|
|
58
58
|
entitlementScope,
|
|
59
59
|
standardKeysFrom
|
|
@@ -1073,13 +1073,13 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
|
|
|
1073
1073
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
1074
1074
|
body: JSON.stringify({ ...args, stream: false })
|
|
1075
1075
|
});
|
|
1076
|
-
const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-
|
|
1076
|
+
const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-CWPQQTBQ.js");
|
|
1077
1077
|
let payload;
|
|
1078
1078
|
if (TOOLS_REGISTRY2.some((t) => t.name === name && t.audiences.includes("mcp"))) {
|
|
1079
1079
|
const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
|
|
1080
1080
|
payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
|
|
1081
1081
|
} else {
|
|
1082
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
1082
|
+
const res = name === "ask" ? await (await import("../../ask-RIWVOI3D.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-2PKZJMUV.js")).handleSearch(env, ctx, inner, tier, key);
|
|
1083
1083
|
payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
1084
1084
|
}
|
|
1085
1085
|
telemetry(env, ctx, tier, `mcp:${name}`, null, !payload?.error, JSON.stringify(payload).length, await sha256Hex(`${name}:${JSON.stringify(args ?? {})}`), void 0, void 0, { durationMs: Date.now() - t0 });
|
|
@@ -2,12 +2,14 @@ import {
|
|
|
2
2
|
cacheableAnswer,
|
|
3
3
|
canonicalRefusal,
|
|
4
4
|
exciseMisplacedPin,
|
|
5
|
-
refusalAnswer
|
|
6
|
-
|
|
5
|
+
refusalAnswer,
|
|
6
|
+
refusalShaped
|
|
7
|
+
} from "../../chunk-ETLSWSZ7.js";
|
|
7
8
|
import "../../chunk-3FYJM7LH.js";
|
|
8
9
|
export {
|
|
9
10
|
cacheableAnswer,
|
|
10
11
|
canonicalRefusal,
|
|
11
12
|
exciseMisplacedPin,
|
|
12
|
-
refusalAnswer
|
|
13
|
+
refusalAnswer,
|
|
14
|
+
refusalShaped
|
|
13
15
|
};
|
package/docs/API.md
CHANGED
|
@@ -410,7 +410,11 @@ never an invented grounding).
|
|
|
410
410
|
|
|
411
411
|
`similar: true` means the answer came from the semantic cache (a
|
|
412
412
|
near-identical recent question). `follow_ups` are suggested next
|
|
413
|
-
questions (present when understanding produced them)
|
|
413
|
+
questions (present when understanding produced them) — and are
|
|
414
|
+
withheld entirely on refusal-shaped answers: the chips are generated
|
|
415
|
+
before retrieval, so after an out-of-corpus refusal they would
|
|
416
|
+
advertise the very topic the corpus does not carry. Honest absence
|
|
417
|
+
extends to the conversation's next step.
|
|
414
418
|
|
|
415
419
|
**Citation object** (also the shape in the `done`/JSON responses):
|
|
416
420
|
```jsonc
|
package/package.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"playwright": "^1.62.1",
|
|
28
28
|
"yaml": "^2.9.1"
|
|
29
29
|
},
|
|
30
|
-
"version": "0.2.
|
|
30
|
+
"version": "0.2.146",
|
|
31
31
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
32
32
|
"license": "BSD-3-Clause",
|
|
33
33
|
"type": "module",
|
|
@@ -17,7 +17,7 @@ import { reflect } from "./reflect";
|
|
|
17
17
|
import { scoreFaithfulness } from "./faithfulness";
|
|
18
18
|
import { entailmentVerdict } from "./entailment";
|
|
19
19
|
import { checkQuoteAnchors, ANCHOR_CORRECTION_NOTE } from "./anchors";
|
|
20
|
-
import { cacheableAnswer, canonicalRefusal, exciseMisplacedPin } from "./refusal";
|
|
20
|
+
import { cacheableAnswer, canonicalRefusal, exciseMisplacedPin, refusalShaped } from "./refusal";
|
|
21
21
|
import { contractV2, tableRetyped } from "./refs";
|
|
22
22
|
import { completeTables, completeFigures } from "./completion";
|
|
23
23
|
import { NO_CONTEXT, appliedContext, contextNote, namedDocumentIn, parseContext, resolveDocScope, syntheticUnderstanding } from "./context";
|
|
@@ -1578,7 +1578,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1578
1578
|
const c2 = canonical0.includes(refusalAnswer())
|
|
1579
1579
|
? { text: canonical0, blocks: [], dropped: [] as string[] }
|
|
1580
1580
|
: await contractV2(env.DB, canonical0, usedHits);
|
|
1581
|
-
send({ type: "done", model, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : [])], context_applied: ctxApplied, read: readAs(),
|
|
1581
|
+
send({ type: "done", model, query_hash: queryHash, follow_ups: refusalShaped(canonical0, refusalScope(answerCites(full))) ? [] : (understanding?.follow_ups ?? []), blocks: [...c2.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : [])], context_applied: ctxApplied, read: readAs(),
|
|
1582
1582
|
// the evidence view's ground truth: the exact passages this
|
|
1583
1583
|
// answer was built from, compact — cache hits carry none,
|
|
1584
1584
|
// because the cache stores the answer and never the passages
|
|
@@ -1849,7 +1849,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1849
1849
|
if (v.support !== "supported") console.log("entailment:", v.support, raced.score, raced.ungrounded_claims?.slice(0, 2));
|
|
1850
1850
|
}
|
|
1851
1851
|
}
|
|
1852
|
-
const out = { answer, citations: finalCites, ...(jsonQuality ? { source_quality: jsonQuality, confidence_note: entailmentNote || qualityNote(jsonQuality), ...(entailment ? { entailment } : {}), ...(jsonQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(finalCites) } : {}) } : {}), model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : []), ...(registerBlock ? [registerBlock] : []), ...completionBlocks], context_applied: ctxApplied, ...(liveRecords ? { records: liveRecords } : {}), ...(ablate ? { ablate } : {}), route: { route: routed.route, features: routed.features } };
|
|
1852
|
+
const out = { answer, citations: finalCites, ...(jsonQuality ? { source_quality: jsonQuality, confidence_note: entailmentNote || qualityNote(jsonQuality), ...(entailment ? { entailment } : {}), ...(jsonQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(finalCites) } : {}) } : {}), model: MODELS.member, query_hash: queryHash, follow_ups: refusalShaped(answer, refusalScope(answerCites(answer))) ? [] : (understanding?.follow_ups ?? []), blocks: [...c2ns.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : []), ...(registerBlock ? [registerBlock] : []), ...completionBlocks], context_applied: ctxApplied, ...(liveRecords ? { records: liveRecords } : {}), ...(ablate ? { ablate } : {}), route: { route: routed.route, features: routed.features } };
|
|
1853
1853
|
const cacheable = !contextual && !declaredCtx && !ablate && cacheableAnswer(answer, refusalScope(answerCites(answer))) && finalAnchors.violations.length === 0;
|
|
1854
1854
|
if (cacheable) {
|
|
1855
1855
|
const ck = exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt)));
|
|
@@ -158,3 +158,12 @@ export function cacheableAnswer(answer: string, scope?: RefusalScope): boolean {
|
|
|
158
158
|
if (answer.includes(refusalAnswer())) return false;
|
|
159
159
|
return canonicalRefusal(answer, scope) === answer;
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
/** The follow-up-chips law (2026-10-13): the understanding stage's
|
|
163
|
+
* follow_ups are generated BEFORE retrieval and answer — after a
|
|
164
|
+
* refusal they advertise the very topic the corpus does not carry.
|
|
165
|
+
* Chips serve only when the answer is not refusal-shaped (carries no
|
|
166
|
+
* pin and the wall would not rewrite it). */
|
|
167
|
+
export function refusalShaped(answer: string, scope?: RefusalScope): boolean {
|
|
168
|
+
return !cacheableAnswer(answer, scope);
|
|
169
|
+
}
|
|
@@ -7,12 +7,28 @@
|
|
|
7
7
|
// on_violation word ("invalid"/"void") when any fails; "void" plus the
|
|
8
8
|
// missing parameter names when the question does not state enough —
|
|
9
9
|
// honest degradation, never a guess.
|
|
10
|
+
//
|
|
11
|
+
// Uncertainty ranges (values stated with a tolerance — "30000 ± 60 v",
|
|
12
|
+
// "30000 pm 0.2 %", the AsciiMath spelling included) are first-class:
|
|
13
|
+
// the check is evaluated across the stated band, and a band that spans
|
|
14
|
+
// both outcomes voids with its reason — guarded acceptance, never a
|
|
15
|
+
// silent worst-case ruling.
|
|
10
16
|
|
|
11
17
|
export interface MachineCheck {
|
|
12
18
|
expression: string;
|
|
13
19
|
symbolic: string;
|
|
14
20
|
values: Record<string, number>;
|
|
15
21
|
result: boolean | null; // null: could not evaluate (missing params)
|
|
22
|
+
/** present when the question states a tolerance on a symbol this
|
|
23
|
+
* check reads: the band's corner evaluations beside the nominal */
|
|
24
|
+
uncertainty?: {
|
|
25
|
+
symbol: string;
|
|
26
|
+
stated: string;
|
|
27
|
+
tol: number;
|
|
28
|
+
unit?: string;
|
|
29
|
+
nominal: boolean;
|
|
30
|
+
corners: (boolean | null)[];
|
|
31
|
+
};
|
|
16
32
|
}
|
|
17
33
|
|
|
18
34
|
export interface Verdict {
|
|
@@ -196,22 +212,94 @@ export function extractParams(query: string, symbols: string[]): Record<string,
|
|
|
196
212
|
return extractParamsWithUnits(query, symbols).values;
|
|
197
213
|
}
|
|
198
214
|
|
|
215
|
+
/** A stated uncertainty: "± 60 v" / "pm 60 v" / "± 0.2 %". `tol` is
|
|
216
|
+
* absolute (a relative statement resolves against the stated value);
|
|
217
|
+
* `stated` quotes the source form for narration. */
|
|
218
|
+
export interface StatedUncertainty {
|
|
219
|
+
tol: number;
|
|
220
|
+
stated: string;
|
|
221
|
+
relative: boolean;
|
|
222
|
+
unit?: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const TOL_SEP_SRC = "\\u00b1|\\bpm\\b|\\+/-";
|
|
226
|
+
const NUM_SRC = "[0-9][0-9 ,.]*[0-9]";
|
|
227
|
+
const UNIT_SRC = "%|\\u00b0?[A-Za-z\\u00b5\\u03a9]{0,6}";
|
|
228
|
+
|
|
229
|
+
function statedUncertainty(
|
|
230
|
+
value: number,
|
|
231
|
+
tolRaw: string,
|
|
232
|
+
tolUnitRaw: string | undefined,
|
|
233
|
+
valueUnit: string | undefined,
|
|
234
|
+
): StatedUncertainty {
|
|
235
|
+
const tolNum = parseNumber(tolRaw);
|
|
236
|
+
const tolUnit = (tolUnitRaw ?? "").trim();
|
|
237
|
+
const relative = tolUnit === "%";
|
|
238
|
+
return {
|
|
239
|
+
tol: relative ? Math.abs(value) * tolNum / 100 : tolNum,
|
|
240
|
+
stated: relative ? `± ${tolRaw} %` : `± ${tolRaw}${tolUnit ? ` ${tolUnit}` : ""}`,
|
|
241
|
+
relative,
|
|
242
|
+
unit: relative ? valueUnit : (tolUnit || valueUnit),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
199
246
|
/** The stated values WITH their unit tokens (TODO.sota/04's unit
|
|
200
247
|
* semantics): "E_max 30000 v" binds e_max=30000 and the unit "v" —
|
|
201
|
-
* the dimension check needs both.
|
|
248
|
+
* the dimension check needs both. A tolerance after the value —
|
|
249
|
+
* "30000 ± 60 v", "30000 v ± 60", "± 0.2 %", or the AsciiMath
|
|
250
|
+
* spelling "30000 pm 60 v" — binds a first-class uncertainty. */
|
|
202
251
|
export function extractParamsWithUnits(
|
|
203
252
|
query: string,
|
|
204
253
|
symbols: string[],
|
|
205
|
-
): {
|
|
254
|
+
): {
|
|
255
|
+
values: Record<string, number>;
|
|
256
|
+
units: Record<string, string>;
|
|
257
|
+
uncertainties: Record<string, StatedUncertainty>;
|
|
258
|
+
} {
|
|
206
259
|
const values: Record<string, number> = {};
|
|
207
260
|
const units: Record<string, string> = {};
|
|
261
|
+
const uncertainties: Record<string, StatedUncertainty> = {};
|
|
208
262
|
for (const sym of symbols) {
|
|
209
263
|
const leaf = sym.split(".").pop() ?? sym;
|
|
210
264
|
const esc = leaf.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
const
|
|
265
|
+
// tolerance first ("value ± tol unit" — the tol's unit names the
|
|
266
|
+
// shared unit when the value carries none), then value and unit
|
|
267
|
+
// side by side with a trailing tolerance, then the plain form
|
|
268
|
+
const reTolFirst = new RegExp(
|
|
269
|
+
`\\b${esc}\\b\\D{0,14}?(${NUM_SRC})\\s*(?:${TOL_SEP_SRC})\\s*(${NUM_SRC})\\s*(${UNIT_SRC})?`,
|
|
270
|
+
"iu",
|
|
271
|
+
);
|
|
272
|
+
const reTolAfter = new RegExp(
|
|
273
|
+
`\\b${esc}\\b\\D{0,14}?(${NUM_SRC})\\s*(${UNIT_SRC})\\s*(?:${TOL_SEP_SRC})\\s*(${NUM_SRC})\\s*(${UNIT_SRC})?`,
|
|
274
|
+
"iu",
|
|
275
|
+
);
|
|
276
|
+
const rePlain = new RegExp(
|
|
277
|
+
`\\b${esc}\\b\\D{0,14}?(${NUM_SRC})\\s*(${UNIT_SRC})?`,
|
|
278
|
+
"iu",
|
|
279
|
+
);
|
|
280
|
+
const mFirst = query.match(reTolFirst);
|
|
281
|
+
const mAfter = mFirst ? null : query.match(reTolAfter);
|
|
282
|
+
const mTol = mFirst ?? mAfter;
|
|
283
|
+
if (mTol) {
|
|
284
|
+
const value = parseNumber(mTol[1]!);
|
|
285
|
+
if (!Number.isFinite(value)) continue;
|
|
286
|
+
values[sym] = value;
|
|
287
|
+
let unit: string | undefined;
|
|
288
|
+
let u: StatedUncertainty;
|
|
289
|
+
if (mFirst) {
|
|
290
|
+
// [2]=tol, [3]=tol unit (shared with the value when present)
|
|
291
|
+
unit = (mFirst[3] ?? "").trim() || undefined;
|
|
292
|
+
u = statedUncertainty(value, mFirst[2]!, mFirst[3], unit);
|
|
293
|
+
} else {
|
|
294
|
+
// [2]=value unit, [3]=tol, [4]=tol unit (defaults to the value's)
|
|
295
|
+
unit = (mAfter![2] ?? "").trim() || undefined;
|
|
296
|
+
u = statedUncertainty(value, mAfter![3]!, mAfter![4], unit || undefined);
|
|
297
|
+
}
|
|
298
|
+
if (unit) units[sym] = unit;
|
|
299
|
+
uncertainties[sym] = u;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const m = query.match(rePlain);
|
|
215
303
|
if (m) {
|
|
216
304
|
const v = parseNumber(m[1]!);
|
|
217
305
|
if (Number.isFinite(v)) {
|
|
@@ -221,7 +309,7 @@ export function extractParamsWithUnits(
|
|
|
221
309
|
}
|
|
222
310
|
}
|
|
223
311
|
}
|
|
224
|
-
return { values, units };
|
|
312
|
+
return { values, units, uncertainties };
|
|
225
313
|
}
|
|
226
314
|
|
|
227
315
|
/** Dimension coherence (TODO.sota/04 item 2): the units plane gives
|
|
@@ -261,10 +349,13 @@ export function evaluate(content: unknown, query: string, opts?: { dimensions?:
|
|
|
261
349
|
let voidReason: string | null = null;
|
|
262
350
|
const machine: MachineCheck[] = checks.map((expression) => {
|
|
263
351
|
const values: Record<string, number> = {};
|
|
352
|
+
const ids: string[] = [];
|
|
264
353
|
try {
|
|
265
|
-
for (const t of tokenize(expression)) if (t.t === "id"
|
|
354
|
+
for (const t of tokenize(expression)) if (t.t === "id") ids.push(t.v);
|
|
266
355
|
} catch { /* values stay partial */ }
|
|
356
|
+
for (const t of new Set(ids)) if (params[t] !== undefined) values[t] = params[t];
|
|
267
357
|
let result: boolean | null = null;
|
|
358
|
+
let uncertainty: MachineCheck["uncertainty"];
|
|
268
359
|
if (missing.length === 0) {
|
|
269
360
|
// unit semantics (TODO.sota/04): a comparison mixing dimensions
|
|
270
361
|
// cannot evaluate — void with the reason, never a number answer
|
|
@@ -273,9 +364,39 @@ export function evaluate(content: unknown, query: string, opts?: { dimensions?:
|
|
|
273
364
|
voidReason = voidReason ?? mismatch;
|
|
274
365
|
} else {
|
|
275
366
|
try { result = !!parseAndEval(expression, params); } catch { result = null; }
|
|
367
|
+
// uncertainty band: the check re-evaluates at every corner of
|
|
368
|
+
// the stated tolerances (2^k ≤ 8); a band spanning both
|
|
369
|
+
// outcomes is undecidable as stated — void with its reason
|
|
370
|
+
const bandSyms = [...new Set(ids)].filter((s) => bound.uncertainties[s] !== undefined).slice(0, 3);
|
|
371
|
+
if (bandSyms.length && result !== null) {
|
|
372
|
+
const corners: (boolean | null)[] = [];
|
|
373
|
+
for (let mask = 0; mask < 1 << bandSyms.length; mask++) {
|
|
374
|
+
const corner = { ...params };
|
|
375
|
+
bandSyms.forEach((s, i) => { corner[s] = corner[s]! + ((mask >> i) & 1 ? 1 : -1) * bound.uncertainties[s]!.tol; });
|
|
376
|
+
try { corners.push(!!parseAndEval(expression, corner)); } catch { corners.push(null); }
|
|
377
|
+
}
|
|
378
|
+
const first = bandSyms[0]!;
|
|
379
|
+
const un = bound.uncertainties[first]!;
|
|
380
|
+
uncertainty = {
|
|
381
|
+
symbol: first,
|
|
382
|
+
stated: un.stated,
|
|
383
|
+
tol: un.tol,
|
|
384
|
+
unit: un.unit,
|
|
385
|
+
nominal: result,
|
|
386
|
+
corners,
|
|
387
|
+
};
|
|
388
|
+
const decided = corners.filter((c): c is boolean => c !== null);
|
|
389
|
+
if (decided.length && decided.some((c) => c !== result)) {
|
|
390
|
+
voidReason = voidReason ?? (
|
|
391
|
+
`the stated uncertainty (${un.stated}) on ${first.split(".").pop()} crosses the decision boundary — ` +
|
|
392
|
+
`the check evaluates ${result ? "true" : "false"} at the stated value but ` +
|
|
393
|
+
`${decided.find((c) => c !== result) ? "true" : "false"} at the band edge; the question cannot be decided as stated`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
276
397
|
}
|
|
277
398
|
}
|
|
278
|
-
return { expression, symbolic: expression, values, result };
|
|
399
|
+
return { expression, symbolic: expression, values, result, uncertainty };
|
|
279
400
|
});
|
|
280
401
|
if (missing.length) {
|
|
281
402
|
return { verdict: "void", missing, checks: machine };
|
|
@@ -286,6 +407,9 @@ export function evaluate(content: unknown, query: string, opts?: { dimensions?:
|
|
|
286
407
|
if (voidReason) return { verdict: "void", missing: [], void_reason: voidReason, checks: machine };
|
|
287
408
|
return null;
|
|
288
409
|
}
|
|
410
|
+
// a stated uncertainty spanning both outcomes — guarded acceptance:
|
|
411
|
+
// void with the reason, never a silent worst-case ruling
|
|
412
|
+
if (voidReason) return { verdict: "void", missing: [], void_reason: voidReason, checks: machine };
|
|
289
413
|
return {
|
|
290
414
|
verdict: failed ? "fail" : "pass",
|
|
291
415
|
on_violation: failed ? String(c.on_violation ?? "invalid") : undefined,
|
|
@@ -303,6 +427,11 @@ export function verdictNote(v: Verdict, node: { node_id: string; clause?: { urn?
|
|
|
303
427
|
for (const c of v.checks) {
|
|
304
428
|
const vals = Object.entries(c.values).map(([k, n]) => `${k}=${n}`).join(", ");
|
|
305
429
|
lines.push(`- ${c.expression}${vals ? ` [${vals}]` : ""} → ${c.result === null ? "not evaluated" : c.result ? "holds" : "VIOLATED"}`);
|
|
430
|
+
if (c.uncertainty) {
|
|
431
|
+
const u = c.uncertainty;
|
|
432
|
+
const edges = u.corners.map((r) => (r === null ? "not evaluated" : r ? "holds" : "VIOLATED")).join("; ");
|
|
433
|
+
lines.push(` band ${u.stated}${u.unit && !u.stated.includes(u.unit) ? ` ${u.unit}` : ""} on ${u.symbol}: ${edges} across the band`);
|
|
434
|
+
}
|
|
306
435
|
}
|
|
307
436
|
if (v.verdict === "void") {
|
|
308
437
|
lines.push(v.void_reason
|