@konneal/engine 0.2.119 → 0.2.121

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.
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-EGFTNES5.js";
4
- import "./chunk-32UDKBA2.js";
3
+ } from "./chunk-YMRBMZNT.js";
4
+ import "./chunk-FKG5VGOL.js";
5
5
  import "./chunk-IIQWESHC.js";
6
6
  import "./chunk-TH5XGPD5.js";
7
7
  import "./chunk-DJ65EF5G.js";
8
- import "./chunk-A3QHHUN5.js";
8
+ import "./chunk-A4A26BCV.js";
9
9
  import "./chunk-4NXH5ZI6.js";
10
10
  import "./chunk-VT7DR6NQ.js";
11
11
  import "./chunk-JZFXSTYX.js";
@@ -0,0 +1,89 @@
1
+ import {
2
+ P
3
+ } from "./chunk-3FYJM7LH.js";
4
+
5
+ // workers/worker_public/src/refusal.ts
6
+ function refusalAnswer() {
7
+ return P().prompts.vars.refusal_sentence;
8
+ }
9
+ function refusalPatterns(publisher) {
10
+ return {
11
+ variant: new RegExp(`^\\s*I don[\u2019']?t have information on .{1,120}? in the indexed ${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\.?`, "i"),
12
+ drift: [
13
+ new RegExp(`\\b(can'?t|cannot|couldn'?t|unable)\\b[^.]{0,120}?\\b(indexed )?${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\b`, "i"),
14
+ new RegExp(`\\bno real answer to give\\b[^.]{0,120}?\\b${publisher}\\b`, "i"),
15
+ /\b(?:falls|well) outside\b[^.]{0,120}?\b(?:what I can answer|my scope|the scope of)\b/i,
16
+ /\boutside (?:of )?what (?:I|this service) can answer\b/i,
17
+ // "I can't answer that — weather forecasting is outside my scope":
18
+ // requires the refusal verb, so a scope DISCUSSION inside a real answer
19
+ // ("this exemption is outside the scope of R 60") never matches
20
+ /\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
21
+ new RegExp(`^\\s*I don[\u2019']?t have any indexed ${publisher} \\w+(?:s)? (?:covering|about|on)\b`, "im"),
22
+ // trivia refusals in the model's own words (the refuse-trivia
23
+ // flake): out-of-domain content named plainly — the refusal
24
+ // verb is the guard, a scope DISCUSSION inside a real answer
25
+ // ("this exemption is outside the scope of R 60") never matches
26
+ /\bI don[’']?t have (?:any )?information on [^.]{0,80}(?:that[’']?s|—)?\s*(?:general|common) knowledge\b/i,
27
+ /\b(?:that[’']?s|this is) (?:just )?(?:general|common) knowledge[^.]{0,80}(?:outside|beyond|not covered)\b/i
28
+ ],
29
+ // the misplaced-pin wall's subset: every pattern here carries a
30
+ // first-person refusal verb ("I don't have information", "I can't
31
+ // answer", "that's general knowledge") — a cited answer's scope
32
+ // DISCUSSION ("outside the scope of R 60's requirements") never
33
+ // matches, so the wall can never eat real content
34
+ wallSafe: [
35
+ new RegExp(`\\b(can'?t|cannot|couldn'?t|unable)\\b[^.]{0,120}?\\b(indexed )?${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\b`, "i"),
36
+ new RegExp(`\\bno real answer to give\\b[^.]{0,120}?\\b${publisher}\\b`, "i"),
37
+ /\bI don[’']?t have (?:any )?information on [^.]{0,80}(?:that[’']?s|—)?\s*(?:general|common) knowledge\b/i,
38
+ /\b(?:that[’']?s|this is) (?:just )?(?:general|common) knowledge[^.]{0,80}(?:outside|beyond|not covered)\b/i,
39
+ /\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i
40
+ ]
41
+ };
42
+ }
43
+ function sentenceStart(answer, i) {
44
+ let s = 0;
45
+ for (const sep of [". ", "! ", "? ", "\n"]) {
46
+ const j = answer.lastIndexOf(sep, i);
47
+ if (j >= 0) s = Math.max(s, j + sep.length);
48
+ }
49
+ return s;
50
+ }
51
+ function sentenceEnd(answer, i) {
52
+ const m = /[.!?\n]/.exec(answer.slice(i));
53
+ return m ? i + m.index + 1 : answer.length;
54
+ }
55
+ function canonicalRefusal(answer) {
56
+ const CANON = refusalAnswer();
57
+ if (answer.includes(CANON)) return answer;
58
+ const { variant: REFUSAL_VARIANT, drift: REFUSAL_DRIFT } = refusalPatterns(P().publisher.name);
59
+ const variant = answer.match(REFUSAL_VARIANT);
60
+ if (variant) return answer.replace(variant[0], CANON);
61
+ for (const drift of REFUSAL_DRIFT) {
62
+ const m = drift.exec(answer);
63
+ if (!m) continue;
64
+ return answer.slice(0, sentenceStart(answer, m.index)) + CANON + answer.slice(sentenceEnd(answer, m.index));
65
+ }
66
+ return answer;
67
+ }
68
+ function exciseMisplacedPin(answer, cites) {
69
+ if (!cites) return answer;
70
+ const CANON = refusalAnswer();
71
+ let out = answer;
72
+ if (out.includes(CANON)) {
73
+ const i = out.indexOf(CANON);
74
+ out = out.slice(0, sentenceStart(out, i)) + out.slice(sentenceEnd(out, i));
75
+ }
76
+ const { wallSafe } = refusalPatterns(P().publisher.name);
77
+ for (const drift of wallSafe) {
78
+ const m = drift.exec(out);
79
+ if (!m) continue;
80
+ out = out.slice(0, sentenceStart(out, m.index)) + out.slice(sentenceEnd(out, m.index));
81
+ }
82
+ return out.replace(/^[ \t\n]+/, "").replace(/\n{3,}/g, "\n\n").trim();
83
+ }
84
+
85
+ export {
86
+ refusalAnswer,
87
+ canonicalRefusal,
88
+ exciseMisplacedPin
89
+ };
@@ -268,27 +268,47 @@ function parseNumber(raw) {
268
268
  s = s.replace(/\.(\d{3})$/, "$1");
269
269
  return Number(s.replace(/,(?=\d{3}\b)/g, ""));
270
270
  }
271
- function extractParams(query, symbols) {
272
- const params = {};
271
+ function extractParamsWithUnits(query, symbols) {
272
+ const values = {};
273
+ const units = {};
273
274
  for (const sym of symbols) {
274
275
  const leaf = sym.split(".").pop() ?? sym;
275
276
  const esc = leaf.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
276
- const re = new RegExp(`\\b${esc}\\b\\D{0,14}?([0-9][0-9 ,.]*[0-9])`, "iu");
277
+ const re = new RegExp(`\\b${esc}\\b\\D{0,14}?([0-9][0-9 ,.]*[0-9])\\s*(%|\\u00b0?[A-Za-z\\u00b5\\u03a9]{0,6})?`, "iu");
277
278
  const m = query.match(re);
278
279
  if (m) {
279
280
  const v = parseNumber(m[1]);
280
- if (Number.isFinite(v)) params[sym] = v;
281
+ if (Number.isFinite(v)) {
282
+ values[sym] = v;
283
+ const u = (m[2] ?? "").trim();
284
+ if (u) units[sym] = u;
285
+ }
281
286
  }
282
287
  }
283
- return params;
288
+ return { values, units };
289
+ }
290
+ function dimensionMismatch(check, units, dimensions) {
291
+ const dims = /* @__PURE__ */ new Map();
292
+ for (const [sym, unit] of Object.entries(units)) {
293
+ const leaf = sym.split(".").pop();
294
+ if (!leaf || !new RegExp(`\\b${leaf}\\b`, "i").test(check)) continue;
295
+ const dim = dimensions[unit.toLowerCase()];
296
+ if (dim) dims.set(sym, dim);
297
+ }
298
+ const distinct = [...new Set(dims.values())];
299
+ if (distinct.length <= 1) return null;
300
+ const parts = [...dims.entries()].map(([sym, dim]) => `${sym} is in ${dim}`);
301
+ return `the stated quantities mix dimensions: ${parts.join(" while ")}`;
284
302
  }
285
- function evaluate(content, query) {
303
+ function evaluate(content, query, opts) {
286
304
  const c = content && typeof content === "object" ? content : {};
287
305
  const checks = extractChecks(content);
288
306
  if (!checks.length) return null;
289
307
  const symbols = symbolsIn(checks);
290
- const params = extractParams(query, symbols);
308
+ const bound = extractParamsWithUnits(query, symbols);
309
+ const params = bound.values;
291
310
  const missing = symbols.filter((s) => params[s] === void 0);
311
+ let voidReason = null;
292
312
  const machine = checks.map((expression) => {
293
313
  const values = {};
294
314
  try {
@@ -297,10 +317,15 @@ function evaluate(content, query) {
297
317
  }
298
318
  let result = null;
299
319
  if (missing.length === 0) {
300
- try {
301
- result = !!parseAndEval(expression, params);
302
- } catch {
303
- result = null;
320
+ const mismatch = opts?.dimensions ? dimensionMismatch(expression, bound.units, opts.dimensions) : null;
321
+ if (mismatch) {
322
+ voidReason = voidReason ?? mismatch;
323
+ } else {
324
+ try {
325
+ result = !!parseAndEval(expression, params);
326
+ } catch {
327
+ result = null;
328
+ }
304
329
  }
305
330
  }
306
331
  return { expression, symbolic: expression, values, result };
@@ -310,7 +335,10 @@ function evaluate(content, query) {
310
335
  }
311
336
  const failed = machine.some((m) => m.result === false);
312
337
  const evaluable = machine.some((m) => m.result !== null);
313
- if (!evaluable) return null;
338
+ if (!evaluable) {
339
+ if (voidReason) return { verdict: "void", missing: [], void_reason: voidReason, checks: machine };
340
+ return null;
341
+ }
314
342
  return {
315
343
  verdict: failed ? "fail" : "pass",
316
344
  on_violation: failed ? String(c.on_violation ?? "invalid") : void 0,
@@ -328,7 +356,7 @@ function verdictNote(v, node) {
328
356
  lines.push(`- ${c.expression}${vals ? ` [${vals}]` : ""} \u2192 ${c.result === null ? "not evaluated" : c.result ? "holds" : "VIOLATED"}`);
329
357
  }
330
358
  if (v.verdict === "void") {
331
- lines.push(`VERDICT: VOID \u2014 the question does not state: ${v.missing.join(", ")}. Say exactly what is missing; never assume values.`);
359
+ 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.`);
332
360
  } else if (v.verdict === "pass") {
333
361
  lines.push(`VERDICT: PASS \u2014 every machine check holds at the stated values. Present this verdict, the arithmetic above, and cite the node's clause.`);
334
362
  } else {
@@ -1179,6 +1207,9 @@ ${r.output}`;
1179
1207
  }
1180
1208
 
1181
1209
  export {
1210
+ extractChecks,
1211
+ symbolsIn,
1212
+ extractParamsWithUnits,
1182
1213
  evaluate,
1183
1214
  verdictNote,
1184
1215
  standardForDocNumber,
@@ -5,6 +5,8 @@ import {
5
5
  contractV2,
6
6
  evaluate,
7
7
  evaluateConditionSets,
8
+ extractChecks,
9
+ extractParamsWithUnits,
8
10
  licenseBoundaryNote,
9
11
  licenseBoundaryRefusal,
10
12
  licensedEntryForPackage,
@@ -16,10 +18,11 @@ import {
16
18
  resolveBlocks,
17
19
  runTool,
18
20
  standardForDocNumber,
21
+ symbolsIn,
19
22
  tableRetyped,
20
23
  toolNote,
21
24
  verdictNote
22
- } from "./chunk-32UDKBA2.js";
25
+ } from "./chunk-FKG5VGOL.js";
23
26
  import {
24
27
  NO_CONTEXT,
25
28
  STAGE_NAMES,
@@ -76,8 +79,9 @@ import {
76
79
  } from "./chunk-DJ65EF5G.js";
77
80
  import {
78
81
  canonicalRefusal,
82
+ exciseMisplacedPin,
79
83
  refusalAnswer
80
- } from "./chunk-A3QHHUN5.js";
84
+ } from "./chunk-A4A26BCV.js";
81
85
  import {
82
86
  entitlementScope,
83
87
  requestSalt,
@@ -2195,7 +2199,22 @@ ${summary}` }] : [],
2195
2199
  console.log("model plane: bound", boundModel.node_id, `[${boundModel.standard}]`, boundModel.clause?.urn ?? "no-clause", boundModel.gated ? "(gated: license)" : "");
2196
2200
  }
2197
2201
  const modelNote = boundModel && !boundModel.gated ? modelGroundingBlock(boundModel) : void 0;
2198
- const machineVerdict = boundModel && !boundModel.gated && !ablate?.noVerdict ? evaluate(boundModel.content, q.query) : null;
2202
+ let machineVerdict = boundModel && !boundModel.gated && !ablate?.noVerdict ? evaluate(boundModel.content, q.query) : null;
2203
+ if (machineVerdict === null && boundModel && !boundModel.gated) {
2204
+ try {
2205
+ const unitTokens = [...new Set(Object.values(extractParamsWithUnits(q.query, symbolsIn(extractChecks(boundModel.content))).units).map((u) => u.toLowerCase()))];
2206
+ if (unitTokens.length) {
2207
+ const marks = unitTokens.map(() => "?").join(",");
2208
+ const rows = await env.DB.prepare(
2209
+ `SELECT lower(coalesce(json_extract(symbols, '$.ascii'), json_extract(symbols, '$.unicode'))) AS sym, dimension_ascii FROM units_db WHERE lower(coalesce(json_extract(symbols, '$.ascii'), json_extract(symbols, '$.unicode'))) IN (${marks})`
2210
+ ).bind(...unitTokens).all().catch(() => ({ results: [] }));
2211
+ const dimensions = {};
2212
+ for (const r of rows.results ?? []) dimensions[String(r.sym)] = String(r.dimension_ascii ?? "");
2213
+ machineVerdict = evaluate(boundModel.content, q.query, { dimensions });
2214
+ }
2215
+ } catch {
2216
+ }
2217
+ }
2199
2218
  const machineNote = machineVerdict && boundModel ? verdictNote(machineVerdict, boundModel) : void 0;
2200
2219
  const modelNodeRows = async (kind, docNum) => {
2201
2220
  const attempt = (num3) => {
@@ -2547,7 +2566,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2547
2566
  }
2548
2567
  }
2549
2568
  }
2550
- const canonical0 = canonicalRefusal(full);
2569
+ const canonical0 = usedHits.length > 0 ? exciseMisplacedPin(canonicalRefusal(full), true) : canonicalRefusal(full);
2551
2570
  const c2 = canonical0.includes(refusalAnswer()) ? { text: canonical0, blocks: [], dropped: [] } : await contractV2(env.DB, canonical0, usedHits);
2552
2571
  send({
2553
2572
  type: "done",
@@ -2632,6 +2651,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2632
2651
  answer = await generateOnce(env, MODELS.fallback, flat, routeEffort(routed.route));
2633
2652
  }
2634
2653
  if (answer) answer = canonicalRefusal(answer);
2654
+ if (answer && usedHits.length > 0) answer = exciseMisplacedPin(answer, true);
2635
2655
  stageTiming.generate = Date.now() - tGen;
2636
2656
  let used = usedHits;
2637
2657
  if (answer && !answer.includes(refusalAnswer())) {
package/dist/refusal.d.ts CHANGED
@@ -7,3 +7,12 @@ export declare function refusalAnswer(): string;
7
7
  * included) for the pinned one — the redirect tail survives. An answer
8
8
  * outside the refusal family is returned byte-identical. */
9
9
  export declare function canonicalRefusal(answer: string): string;
10
+ /** The misplaced-pin wall (2026-10-10): an answer that actually cites
11
+ * passages is NOT a refusal — but the model sometimes drops the
12
+ * pinned sentence into an otherwise-correct, well-cited answer as a
13
+ * sub-topic disclaimer (measured: the refusal pin at char 435 of a
14
+ * load-cell answer citing R 60). Readers and graders read the pin as
15
+ * a TOTAL refusal. The wall is deterministic: when the answer cites,
16
+ * the pin sentence (and its refusal-shaped paraphrases) are excised —
17
+ * the surrounding content stands on its own. */
18
+ export declare function exciseMisplacedPin(answer: string, cites: boolean): string;
@@ -4,7 +4,7 @@ import {
4
4
  import "./chunk-IIQWESHC.js";
5
5
  import "./chunk-TH5XGPD5.js";
6
6
  import "./chunk-DJ65EF5G.js";
7
- import "./chunk-A3QHHUN5.js";
7
+ import "./chunk-A4A26BCV.js";
8
8
  import "./chunk-4NXH5ZI6.js";
9
9
  import "./chunk-VT7DR6NQ.js";
10
10
  import "./chunk-3FYJM7LH.js";
@@ -4,7 +4,7 @@ import {
4
4
  parseToolCall,
5
5
  runTool,
6
6
  toolNote
7
- } from "./chunk-32UDKBA2.js";
7
+ } from "./chunk-FKG5VGOL.js";
8
8
  import "./chunk-TH5XGPD5.js";
9
9
  import "./chunk-4NXH5ZI6.js";
10
10
  import "./chunk-VT7DR6NQ.js";
package/dist/verdict.d.ts CHANGED
@@ -9,12 +9,30 @@ export interface Verdict {
9
9
  on_violation?: string;
10
10
  violation_meaning?: string;
11
11
  missing: string[];
12
+ /** why a fully-stated question still cannot evaluate (TODO.sota/04):
13
+ * the stated quantities' units carry conflicting dimensions */
14
+ void_reason?: string;
12
15
  checks: MachineCheck[];
13
16
  }
14
17
  export declare function extractChecks(content: unknown): string[];
15
18
  export declare function symbolsIn(checks: string[]): string[];
16
19
  export declare function extractParams(query: string, symbols: string[]): Record<string, number>;
17
- export declare function evaluate(content: unknown, query: string): Verdict | null;
20
+ /** The stated values WITH their unit tokens (TODO.sota/04's unit
21
+ * semantics): "E_max 30000 v" binds e_max=30000 and the unit "v" —
22
+ * the dimension check needs both. */
23
+ export declare function extractParamsWithUnits(query: string, symbols: string[]): {
24
+ values: Record<string, number>;
25
+ units: Record<string, string>;
26
+ };
27
+ /** Dimension coherence (TODO.sota/04 item 2): the units plane gives
28
+ * each unit token a dimension; a parameter set whose bound symbols
29
+ * carry CONFLICTING known dimensions is incoherent — the check
30
+ * compares quantities that are not comparable. Pure: the dimensions
31
+ * map arrives from the caller (the units register at load). */
32
+ export declare function dimensionMismatch(check: string, units: Record<string, string>, dimensions: Record<string, string>): string | null;
33
+ export declare function evaluate(content: unknown, query: string, opts?: {
34
+ dimensions?: Record<string, string>;
35
+ }): Verdict | null;
18
36
  /** The deterministic note the answer model narrates — never recomputes. */
19
37
  export declare function verdictNote(v: Verdict, node: {
20
38
  node_id: string;
@@ -4,12 +4,12 @@ import {
4
4
  handleMemories,
5
5
  scoreFaithfulness,
6
6
  scoreJudge
7
- } from "../../chunk-EGFTNES5.js";
7
+ } from "../../chunk-YMRBMZNT.js";
8
8
  import {
9
9
  TOOLS_REGISTRY,
10
10
  bindModelNode,
11
11
  modelGroundingBlock
12
- } from "../../chunk-32UDKBA2.js";
12
+ } from "../../chunk-FKG5VGOL.js";
13
13
  import {
14
14
  handleSearch
15
15
  } from "../../chunk-C5L55VI7.js";
@@ -52,7 +52,7 @@ import {
52
52
  } from "../../chunk-DJ65EF5G.js";
53
53
  import {
54
54
  canonicalRefusal
55
- } from "../../chunk-A3QHHUN5.js";
55
+ } from "../../chunk-A4A26BCV.js";
56
56
  import {
57
57
  entitlementScope,
58
58
  standardKeysFrom
@@ -1043,13 +1043,13 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
1043
1043
  // stream:false forces the JSON lane (anon defaults to SSE)
1044
1044
  body: JSON.stringify({ ...args, stream: false })
1045
1045
  });
1046
- const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-GSLHFD37.js");
1046
+ const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-CUKFOW7S.js");
1047
1047
  let payload;
1048
1048
  if (TOOLS_REGISTRY2.some((t) => t.name === name && t.audiences.includes("mcp"))) {
1049
1049
  const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
1050
1050
  payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
1051
1051
  } else {
1052
- const res = name === "ask" ? await (await import("../../ask-YVBM6IEV.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-JDSQPI3A.js")).handleSearch(env, ctx, inner, tier, key);
1052
+ const res = name === "ask" ? await (await import("../../ask-5RWMWNX5.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-RMB4BL4P.js")).handleSearch(env, ctx, inner, tier, key);
1053
1053
  payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
1054
1054
  }
1055
1055
  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 });
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  canonicalRefusal,
3
+ exciseMisplacedPin,
3
4
  refusalAnswer
4
- } from "../../chunk-A3QHHUN5.js";
5
+ } from "../../chunk-A4A26BCV.js";
5
6
  import "../../chunk-3FYJM7LH.js";
6
7
  export {
7
8
  canonicalRefusal,
9
+ exciseMisplacedPin,
8
10
  refusalAnswer
9
11
  };
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.119",
30
+ "version": "0.2.121",
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,13 +17,13 @@ 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 { canonicalRefusal } from "./refusal";
20
+ import { canonicalRefusal, exciseMisplacedPin } 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";
24
24
  import { liveDataConfig, liveTokenFor, opCfg, opTokenMember, resolveLiveAccount, type LiveRecord } from "./livedata";
25
25
  import { bindModelNode, bindConstraintByQuantities, licenseBoundaryNote, licenseBoundaryRefusal, licensedEntryForPackage, modelCitation, modelEcho, modelGroundingBlock, standardForDocNumber } from "./modelplane";
26
- import { evaluate as machineEvaluate, verdictNote } from "./verdict";
26
+ import { evaluate as machineEvaluate, extractChecks, extractParamsWithUnits, symbolsIn, verdictNote } from "./verdict";
27
27
  import { evaluateConditionSets, quantitiesIn, type ConditionVerdict } from "./conditions";
28
28
  import { evaluateAggregation, type AggregationVerdict } from "./aggregation";
29
29
  import { matchLicensedTopic, boundaryNoteText } from "./boundary";
@@ -946,7 +946,29 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
946
946
  // the worker EXECUTES the bound node's machine checks against the
947
947
  // question's stated values; the model narrates the computed verdict
948
948
  // and the verdict BLOCK is server-built — data, never generated prose
949
- const machineVerdict = boundModel && !boundModel.gated && !ablate?.noVerdict ? machineEvaluate(boundModel.content, q.query) : null;
949
+ let machineVerdict = boundModel && !boundModel.gated && !ablate?.noVerdict ? machineEvaluate(boundModel.content, q.query) : null;
950
+ if (machineVerdict === null && boundModel && !boundModel.gated) {
951
+ // unit semantics (TODO.sota/04): the stated values' units reach the
952
+ // units register for their dimensions; a comparison mixing
953
+ // dimensions voids with the reason instead of answering a number
954
+ try {
955
+ const unitTokens = [...new Set(Object.values(extractParamsWithUnits(q.query, symbolsIn(extractChecks(boundModel.content))).units).map((u) => u.toLowerCase()))];
956
+ if (unitTokens.length) {
957
+ const marks = unitTokens.map(() => "?").join(",");
958
+ // the register keys symbols in the symbols JSON (ascii/unicode),
959
+ // not in `short` (full names) — D1's json_extract reads them
960
+ const rows = await env.DB.prepare(
961
+ `SELECT lower(coalesce(json_extract(symbols, '$.ascii'), json_extract(symbols, '$.unicode'))) AS sym, dimension_ascii FROM units_db WHERE lower(coalesce(json_extract(symbols, '$.ascii'), json_extract(symbols, '$.unicode'))) IN (${marks})`,
962
+ )
963
+ .bind(...unitTokens).all().catch(() => ({ results: [] }));
964
+ const dimensions: Record<string, string> = {};
965
+ for (const r of (rows.results ?? []) as any[]) dimensions[String(r.sym)] = String(r.dimension_ascii ?? "");
966
+ machineVerdict = machineEvaluate(boundModel.content, q.query, { dimensions });
967
+ }
968
+ } catch {
969
+ // the plain evaluation above already ran; nothing to salvage
970
+ }
971
+ }
950
972
  const machineNote = machineVerdict && boundModel ? verdictNote(machineVerdict, boundModel) : undefined;
951
973
  // ── condition-set membership (konneal/engine#90): the test-method
952
974
  // packages' severity menus as machine-verifiable membership. Fires
@@ -1442,7 +1464,9 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1442
1464
  } catch { /* deliver what we have */ }
1443
1465
  }
1444
1466
  }
1445
- const canonical0 = canonicalRefusal(full);
1467
+ // the misplaced-pin wall rides the streamed path too: a
1468
+ // CITED stream is not a refusal
1469
+ const canonical0 = usedHits.length > 0 ? exciseMisplacedPin(canonicalRefusal(full), true) : canonicalRefusal(full);
1446
1470
  // answer contract v2: validate [[u:]] refs, resolve typed blocks
1447
1471
  const c2 = canonical0.includes(refusalAnswer())
1448
1472
  ? { text: canonical0, blocks: [], dropped: [] as string[] }
@@ -1547,6 +1571,11 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1547
1571
  answer = await generateOnce(env, MODELS.fallback, flat, routeEffort(routed.route));
1548
1572
  }
1549
1573
  if (answer) answer = canonicalRefusal(answer);
1574
+ // the misplaced-pin wall: a CITED answer is not a refusal — excise
1575
+ // the pinned sentence if the model dropped it in as a disclaimer
1576
+ // (measured on chip-scoped asks: correct cited answers carrying the
1577
+ // pin mid-text, read by users and graders as total refusals)
1578
+ if (answer && usedHits.length > 0) answer = exciseMisplacedPin(answer, true);
1550
1579
  stageTiming.generate = Date.now() - tGen;
1551
1580
 
1552
1581
  // ── Deterministic quote-anchor + table-retyping check ──
@@ -10,7 +10,7 @@ export function refusalAnswer(): string {
10
10
  // on how to make lasagna in the indexed..."); the API contract is the
11
11
  // exact canonical sentence — normalize variants, keep the redirect tail.
12
12
  // The patterns are built per call: the publisher token is profile data.
13
- function refusalPatterns(publisher: string): { variant: RegExp; drift: RegExp[] } {
13
+ function refusalPatterns(publisher: string): { variant: RegExp; drift: RegExp[]; wallSafe: RegExp[] } {
14
14
  return {
15
15
  variant: new RegExp(`^\\s*I don[’']?t have information on .{1,120}? in the indexed ${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\.?`, "i"),
16
16
  drift: [
@@ -23,6 +23,24 @@ function refusalPatterns(publisher: string): { variant: RegExp; drift: RegExp[]
23
23
  // ("this exemption is outside the scope of R 60") never matches
24
24
  /\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
25
25
  new RegExp(`^\\s*I don[’']?t have any indexed ${publisher} \\w+(?:s)? (?:covering|about|on)\b`, "im"),
26
+ // trivia refusals in the model's own words (the refuse-trivia
27
+ // flake): out-of-domain content named plainly — the refusal
28
+ // verb is the guard, a scope DISCUSSION inside a real answer
29
+ // ("this exemption is outside the scope of R 60") never matches
30
+ /\bI don[’']?t have (?:any )?information on [^.]{0,80}(?:that[’']?s|—)?\s*(?:general|common) knowledge\b/i,
31
+ /\b(?:that[’']?s|this is) (?:just )?(?:general|common) knowledge[^.]{0,80}(?:outside|beyond|not covered)\b/i,
32
+ ],
33
+ // the misplaced-pin wall's subset: every pattern here carries a
34
+ // first-person refusal verb ("I don't have information", "I can't
35
+ // answer", "that's general knowledge") — a cited answer's scope
36
+ // DISCUSSION ("outside the scope of R 60's requirements") never
37
+ // matches, so the wall can never eat real content
38
+ wallSafe: [
39
+ new RegExp(`\\b(can'?t|cannot|couldn'?t|unable)\\b[^.]{0,120}?\\b(indexed )?${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\b`, "i"),
40
+ new RegExp(`\\bno real answer to give\\b[^.]{0,120}?\\b${publisher}\\b`, "i"),
41
+ /\bI don[’']?t have (?:any )?information on [^.]{0,80}(?:that[’']?s|—)?\s*(?:general|common) knowledge\b/i,
42
+ /\b(?:that[’']?s|this is) (?:just )?(?:general|common) knowledge[^.]{0,80}(?:outside|beyond|not covered)\b/i,
43
+ /\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
26
44
  ],
27
45
  };
28
46
  }
@@ -61,3 +79,29 @@ export function canonicalRefusal(answer: string): string {
61
79
  }
62
80
  return answer;
63
81
  }
82
+
83
+
84
+ /** The misplaced-pin wall (2026-10-10): an answer that actually cites
85
+ * passages is NOT a refusal — but the model sometimes drops the
86
+ * pinned sentence into an otherwise-correct, well-cited answer as a
87
+ * sub-topic disclaimer (measured: the refusal pin at char 435 of a
88
+ * load-cell answer citing R 60). Readers and graders read the pin as
89
+ * a TOTAL refusal. The wall is deterministic: when the answer cites,
90
+ * the pin sentence (and its refusal-shaped paraphrases) are excised —
91
+ * the surrounding content stands on its own. */
92
+ export function exciseMisplacedPin(answer: string, cites: boolean): string {
93
+ if (!cites) return answer;
94
+ const CANON = refusalAnswer();
95
+ let out = answer;
96
+ if (out.includes(CANON)) {
97
+ const i = out.indexOf(CANON);
98
+ out = out.slice(0, sentenceStart(out, i)) + out.slice(sentenceEnd(out, i));
99
+ }
100
+ const { wallSafe } = refusalPatterns(P().publisher.name);
101
+ for (const drift of wallSafe) {
102
+ const m = drift.exec(out);
103
+ if (!m) continue;
104
+ out = out.slice(0, sentenceStart(out, m.index)) + out.slice(sentenceEnd(out, m.index));
105
+ }
106
+ return out.replace(/^[ \t\n]+/, "").replace(/\n{3,}/g, "\n\n").trim();
107
+ }
@@ -20,6 +20,9 @@ export interface Verdict {
20
20
  on_violation?: string;
21
21
  violation_meaning?: string;
22
22
  missing: string[];
23
+ /** why a fully-stated question still cannot evaluate (TODO.sota/04):
24
+ * the stated quantities' units carry conflicting dimensions */
25
+ void_reason?: string;
23
26
  checks: MachineCheck[];
24
27
  }
25
28
 
@@ -190,29 +193,72 @@ function parseNumber(raw: string): number {
190
193
  }
191
194
 
192
195
  export function extractParams(query: string, symbols: string[]): Record<string, number> {
193
- const params: Record<string, number> = {};
196
+ return extractParamsWithUnits(query, symbols).values;
197
+ }
198
+
199
+ /** The stated values WITH their unit tokens (TODO.sota/04's unit
200
+ * semantics): "E_max 30000 v" binds e_max=30000 and the unit "v" —
201
+ * the dimension check needs both. */
202
+ export function extractParamsWithUnits(
203
+ query: string,
204
+ symbols: string[],
205
+ ): { values: Record<string, number>; units: Record<string, string> } {
206
+ const values: Record<string, number> = {};
207
+ const units: Record<string, string> = {};
194
208
  for (const sym of symbols) {
195
209
  const leaf = sym.split(".").pop() ?? sym;
196
210
  const esc = leaf.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
197
211
  // symbol, a short non-numeric gap (words like "were"/"of" allowed),
198
- // then the number
199
- const re = new RegExp(`\\b${esc}\\b\\D{0,14}?([0-9][0-9 ,.]*[0-9])`, "iu");
212
+ // then the number, then an optional unit token directly after
213
+ const re = new RegExp(`\\b${esc}\\b\\D{0,14}?([0-9][0-9 ,.]*[0-9])\\s*(%|\\u00b0?[A-Za-z\\u00b5\\u03a9]{0,6})?`, "iu");
200
214
  const m = query.match(re);
201
215
  if (m) {
202
216
  const v = parseNumber(m[1]!);
203
- if (Number.isFinite(v)) params[sym] = v;
217
+ if (Number.isFinite(v)) {
218
+ values[sym] = v;
219
+ const u = (m[2] ?? "").trim();
220
+ if (u) units[sym] = u;
221
+ }
204
222
  }
205
223
  }
206
- return params;
224
+ return { values, units };
225
+ }
226
+
227
+ /** Dimension coherence (TODO.sota/04 item 2): the units plane gives
228
+ * each unit token a dimension; a parameter set whose bound symbols
229
+ * carry CONFLICTING known dimensions is incoherent — the check
230
+ * compares quantities that are not comparable. Pure: the dimensions
231
+ * map arrives from the caller (the units register at load). */
232
+ export function dimensionMismatch(
233
+ check: string,
234
+ units: Record<string, string>,
235
+ dimensions: Record<string, string>,
236
+ ): string | null {
237
+ // the symbols THIS check compares, each mapped through its stated
238
+ // unit's dimension; more than one distinct dimension in one
239
+ // comparison is incoherent (a length is not a voltage)
240
+ const dims = new Map<string, string>();
241
+ for (const [sym, unit] of Object.entries(units)) {
242
+ const leaf = sym.split(".").pop();
243
+ if (!leaf || !new RegExp(`\\b${leaf}\\b`, "i").test(check)) continue;
244
+ const dim = dimensions[unit.toLowerCase()];
245
+ if (dim) dims.set(sym, dim);
246
+ }
247
+ const distinct = [...new Set(dims.values())];
248
+ if (distinct.length <= 1) return null;
249
+ const parts = [...dims.entries()].map(([sym, dim]) => `${sym} is in ${dim}`);
250
+ return `the stated quantities mix dimensions: ${parts.join(" while ")}`;
207
251
  }
208
252
 
209
- export function evaluate(content: unknown, query: string): Verdict | null {
253
+ export function evaluate(content: unknown, query: string, opts?: { dimensions?: Record<string, string> }): Verdict | null {
210
254
  const c = (content && typeof content === "object" ? content : {}) as Record<string, any>;
211
255
  const checks = extractChecks(content);
212
256
  if (!checks.length) return null;
213
257
  const symbols = symbolsIn(checks);
214
- const params = extractParams(query, symbols);
258
+ const bound = extractParamsWithUnits(query, symbols);
259
+ const params = bound.values;
215
260
  const missing = symbols.filter((s) => params[s] === undefined);
261
+ let voidReason: string | null = null;
216
262
  const machine: MachineCheck[] = checks.map((expression) => {
217
263
  const values: Record<string, number> = {};
218
264
  try {
@@ -220,7 +266,14 @@ export function evaluate(content: unknown, query: string): Verdict | null {
220
266
  } catch { /* values stay partial */ }
221
267
  let result: boolean | null = null;
222
268
  if (missing.length === 0) {
223
- try { result = !!parseAndEval(expression, params); } catch { result = null; }
269
+ // unit semantics (TODO.sota/04): a comparison mixing dimensions
270
+ // cannot evaluate — void with the reason, never a number answer
271
+ const mismatch = opts?.dimensions ? dimensionMismatch(expression, bound.units, opts.dimensions) : null;
272
+ if (mismatch) {
273
+ voidReason = voidReason ?? mismatch;
274
+ } else {
275
+ try { result = !!parseAndEval(expression, params); } catch { result = null; }
276
+ }
224
277
  }
225
278
  return { expression, symbolic: expression, values, result };
226
279
  });
@@ -229,7 +282,10 @@ export function evaluate(content: unknown, query: string): Verdict | null {
229
282
  }
230
283
  const failed = machine.some((m) => m.result === false);
231
284
  const evaluable = machine.some((m) => m.result !== null);
232
- if (!evaluable) return null;
285
+ if (!evaluable) {
286
+ if (voidReason) return { verdict: "void", missing: [], void_reason: voidReason, checks: machine };
287
+ return null;
288
+ }
233
289
  return {
234
290
  verdict: failed ? "fail" : "pass",
235
291
  on_violation: failed ? String(c.on_violation ?? "invalid") : undefined,
@@ -249,7 +305,9 @@ export function verdictNote(v: Verdict, node: { node_id: string; clause?: { urn?
249
305
  lines.push(`- ${c.expression}${vals ? ` [${vals}]` : ""} → ${c.result === null ? "not evaluated" : c.result ? "holds" : "VIOLATED"}`);
250
306
  }
251
307
  if (v.verdict === "void") {
252
- lines.push(`VERDICT: VOID — the question does not state: ${v.missing.join(", ")}. Say exactly what is missing; never assume values.`);
308
+ lines.push(v.void_reason
309
+ ? `VERDICT: VOID — ${v.void_reason}. State the mismatch plainly; the question cannot be decided as stated.`
310
+ : `VERDICT: VOID — the question does not state: ${v.missing.join(", ")}. Say exactly what is missing; never assume values.`);
253
311
  } else if (v.verdict === "pass") {
254
312
  lines.push(`VERDICT: PASS — every machine check holds at the stated values. Present this verdict, the arithmetic above, and cite the node's clause.`);
255
313
  } else {
@@ -1,54 +0,0 @@
1
- import {
2
- P
3
- } from "./chunk-3FYJM7LH.js";
4
-
5
- // workers/worker_public/src/refusal.ts
6
- function refusalAnswer() {
7
- return P().prompts.vars.refusal_sentence;
8
- }
9
- function refusalPatterns(publisher) {
10
- return {
11
- variant: new RegExp(`^\\s*I don[\u2019']?t have information on .{1,120}? in the indexed ${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\.?`, "i"),
12
- drift: [
13
- new RegExp(`\\b(can'?t|cannot|couldn'?t|unable)\\b[^.]{0,120}?\\b(indexed )?${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\b`, "i"),
14
- new RegExp(`\\bno real answer to give\\b[^.]{0,120}?\\b${publisher}\\b`, "i"),
15
- /\b(?:falls|well) outside\b[^.]{0,120}?\b(?:what I can answer|my scope|the scope of)\b/i,
16
- /\boutside (?:of )?what (?:I|this service) can answer\b/i,
17
- // "I can't answer that — weather forecasting is outside my scope":
18
- // requires the refusal verb, so a scope DISCUSSION inside a real answer
19
- // ("this exemption is outside the scope of R 60") never matches
20
- /\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
21
- new RegExp(`^\\s*I don[\u2019']?t have any indexed ${publisher} \\w+(?:s)? (?:covering|about|on)\b`, "im")
22
- ]
23
- };
24
- }
25
- function sentenceStart(answer, i) {
26
- let s = 0;
27
- for (const sep of [". ", "! ", "? ", "\n"]) {
28
- const j = answer.lastIndexOf(sep, i);
29
- if (j >= 0) s = Math.max(s, j + sep.length);
30
- }
31
- return s;
32
- }
33
- function sentenceEnd(answer, i) {
34
- const m = /[.!?\n]/.exec(answer.slice(i));
35
- return m ? i + m.index + 1 : answer.length;
36
- }
37
- function canonicalRefusal(answer) {
38
- const CANON = refusalAnswer();
39
- if (answer.includes(CANON)) return answer;
40
- const { variant: REFUSAL_VARIANT, drift: REFUSAL_DRIFT } = refusalPatterns(P().publisher.name);
41
- const variant = answer.match(REFUSAL_VARIANT);
42
- if (variant) return answer.replace(variant[0], CANON);
43
- for (const drift of REFUSAL_DRIFT) {
44
- const m = drift.exec(answer);
45
- if (!m) continue;
46
- return answer.slice(0, sentenceStart(answer, m.index)) + CANON + answer.slice(sentenceEnd(answer, m.index));
47
- }
48
- return answer;
49
- }
50
-
51
- export {
52
- refusalAnswer,
53
- canonicalRefusal
54
- };