@konneal/engine 0.2.134 → 0.2.136

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-JYJJ4SYF.js";
3
+ } from "./chunk-YLBPABDQ.js";
4
4
  import "./chunk-J6LBR6GI.js";
5
5
  import "./chunk-7AXXR7GD.js";
6
6
  import "./chunk-TH5XGPD5.js";
7
7
  import "./chunk-W7J5SFXJ.js";
8
- import "./chunk-WPOKSNPE.js";
8
+ import "./chunk-XBZIJMS7.js";
9
9
  import "./chunk-4NXH5ZI6.js";
10
10
  import "./chunk-VT7DR6NQ.js";
11
11
  import "./chunk-GBGRHXRU.js";
@@ -97,9 +97,14 @@ function exciseMisplacedPin(answer, cites) {
97
97
  }
98
98
  return out.replace(/^[ \t\n]+/, "").replace(/\n{3,}/g, "\n\n").trim();
99
99
  }
100
+ function cacheableAnswer(answer, scope) {
101
+ if (answer.includes(refusalAnswer())) return false;
102
+ return canonicalRefusal(answer, scope) === answer;
103
+ }
100
104
 
101
105
  export {
102
106
  refusalAnswer,
103
107
  canonicalRefusal,
104
- exciseMisplacedPin
108
+ exciseMisplacedPin,
109
+ cacheableAnswer
105
110
  };
@@ -80,10 +80,11 @@ import {
80
80
  validateQuery
81
81
  } from "./chunk-W7J5SFXJ.js";
82
82
  import {
83
+ cacheableAnswer,
83
84
  canonicalRefusal,
84
85
  exciseMisplacedPin,
85
86
  refusalAnswer
86
- } from "./chunk-WPOKSNPE.js";
87
+ } from "./chunk-XBZIJMS7.js";
87
88
  import {
88
89
  entitlementScope,
89
90
  requestSalt,
@@ -1980,6 +1981,7 @@ async function handleAsk(env, ctx, req, tier, key) {
1980
1981
  }
1981
1982
  }
1982
1983
  let understanding = null;
1984
+ const answerCites = (t) => /\[[^\]]+\]/.test(t);
1983
1985
  const refusalScope = (cited) => ({ cited, conversational: understanding?.intent === "conversational" });
1984
1986
  let optimisticVec = null;
1985
1987
  let optimisticHits = [];
@@ -2621,7 +2623,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2621
2623
  }
2622
2624
  }
2623
2625
  }
2624
- const canonical0 = usedHits.length > 0 ? exciseMisplacedPin(canonicalRefusal(full, refusalScope(usedHits.length > 0)), true) : canonicalRefusal(full, refusalScope(false));
2626
+ const canonical0 = usedHits.length > 0 ? exciseMisplacedPin(canonicalRefusal(full, refusalScope(answerCites(full))), true) : canonicalRefusal(full, refusalScope(false));
2625
2627
  const c2 = canonical0.includes(refusalAnswer()) ? { text: canonical0, blocks: [], dropped: [] } : await contractV2(env.DB, canonical0, usedHits);
2626
2628
  send({
2627
2629
  type: "done",
@@ -2659,7 +2661,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2659
2661
  if (streamed.violations.length > 0) {
2660
2662
  console.log("anchors:", streamed.violations.length, "of", streamed.total, "unverified \u2014 not caching");
2661
2663
  }
2662
- if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && !canonical.includes(refusalAnswer()) && !ablate) {
2664
+ if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && cacheableAnswer(canonical, refusalScope(answerCites(canonical))) && !ablate) {
2663
2665
  ctx.waitUntil(
2664
2666
  env.CACHE.put(exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt))), JSON.stringify({ answer: canonical, citations: cites, model, query_hash: queryHash, ...sourceQuality ? { source_quality: sourceQuality, confidence_note: streamedNote || qualityNote(sourceQuality) } : {}, ...streamedEntailment ? { entailment: streamedEntailment } : {} }), { expirationTtl: LIMITS.cacheTtlSec })
2665
2667
  );
@@ -2705,7 +2707,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2705
2707
  );
2706
2708
  answer = await generateOnce(env, MODELS.fallback, flat, routeEffort(routed.route));
2707
2709
  }
2708
- if (answer) answer = canonicalRefusal(answer, refusalScope(usedHits.length > 0));
2710
+ if (answer) answer = canonicalRefusal(answer, refusalScope(answerCites(answer)));
2709
2711
  if (answer && usedHits.length > 0) answer = exciseMisplacedPin(answer, true);
2710
2712
  stageTiming.generate = Date.now() - tGen;
2711
2713
  let used = usedHits;
@@ -2730,7 +2732,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2730
2732
  generateRetries += 1;
2731
2733
  const corrected = await generateOnce(env, model, [...messages, { role: "system", content: note }], routeEffort(routed.route));
2732
2734
  if (corrected) {
2733
- const correctedAnswer = canonicalRefusal(corrected, refusalScope(usedHits.length > 0));
2735
+ const correctedAnswer = canonicalRefusal(corrected, refusalScope(answerCites(corrected)));
2734
2736
  const retryAnchors = checkQuoteAnchors(correctedAnswer, used.map((h) => h.text));
2735
2737
  const retryRetyped = tableRetyped(correctedAnswer, hasTableUnit);
2736
2738
  if (retryAnchors.violations.length < anchors.violations.length || !retryRetyped && retyped || unreferenced && correctedAnswer.includes("[[u:")) {
@@ -2755,7 +2757,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2755
2757
  const { messages: retryMessages, usedHits: retryUsed } = buildMessages(q.query, retryRetrieve.hits, q.lang, keptHistory, void 0, summary, budget);
2756
2758
  const retryAnswer = await generateOnce(env, model, retryMessages, routeEffort(routed.route));
2757
2759
  if (retryAnswer) {
2758
- answer = canonicalRefusal(retryAnswer, refusalScope(retryRetrieve.hits.length > 0));
2760
+ answer = canonicalRefusal(retryAnswer, refusalScope(answerCites(retryAnswer)));
2759
2761
  used = retryUsed;
2760
2762
  }
2761
2763
  }
@@ -2787,7 +2789,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2787
2789
  messages[messages.length - 1]
2788
2790
  ], effort);
2789
2791
  if (regen && !regen.includes(refusalAnswer())) {
2790
- answer = canonicalRefusal(regen, refusalScope(true));
2792
+ answer = canonicalRefusal(regen, refusalScope(answerCites(regen)));
2791
2793
  console.log("blind-answer: regeneration grounded");
2792
2794
  }
2793
2795
  }
@@ -2807,7 +2809,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2807
2809
  }
2808
2810
  }
2809
2811
  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 } };
2810
- const cacheable = !contextual && !declaredCtx && !ablate && !answer.includes(refusalAnswer()) && finalAnchors.violations.length === 0;
2812
+ const cacheable = !contextual && !declaredCtx && !ablate && cacheableAnswer(answer, refusalScope(answerCites(answer))) && finalAnchors.violations.length === 0;
2811
2813
  if (cacheable) {
2812
2814
  const ck = exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt)));
2813
2815
  ctx.waitUntil(env.CACHE.put(ck, JSON.stringify(out), { expirationTtl: LIMITS.cacheTtlSec }));
package/dist/refusal.d.ts CHANGED
@@ -32,3 +32,11 @@ export declare function canonicalRefusal(answer: string, scope?: RefusalScope):
32
32
  * the pin sentence (and its refusal-shaped paraphrases) are excised —
33
33
  * the surrounding content stands on its own. */
34
34
  export declare function exciseMisplacedPin(answer: string, cites: boolean): string;
35
+ /** Cacheable under the refusal law (2026-10-11, the poisoned-entry
36
+ * lesson): "refusals are never cached" was enforced as "answers
37
+ * containing the pin are never cached" — backwards for DRIFT, which
38
+ * lacks the pin and cached itself across deploy windows (an edge
39
+ * instance running pre-wall code wrote a drifted refusal that served
40
+ * every later ask). An answer is cacheable only when it carries no
41
+ * pin AND the wall would not rewrite it. */
42
+ export declare function cacheableAnswer(answer: string, scope?: RefusalScope): boolean;
@@ -4,7 +4,7 @@ import {
4
4
  import "./chunk-7AXXR7GD.js";
5
5
  import "./chunk-TH5XGPD5.js";
6
6
  import "./chunk-W7J5SFXJ.js";
7
- import "./chunk-WPOKSNPE.js";
7
+ import "./chunk-XBZIJMS7.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
  handleMemories,
5
5
  scoreFaithfulness,
6
6
  scoreJudge
7
- } from "../../chunk-JYJJ4SYF.js";
7
+ } from "../../chunk-YLBPABDQ.js";
8
8
  import {
9
9
  TOOLS_REGISTRY,
10
10
  bindModelNode,
@@ -54,7 +54,7 @@ import {
54
54
  } from "../../chunk-W7J5SFXJ.js";
55
55
  import {
56
56
  canonicalRefusal
57
- } from "../../chunk-WPOKSNPE.js";
57
+ } from "../../chunk-XBZIJMS7.js";
58
58
  import {
59
59
  entitlementScope,
60
60
  standardKeysFrom
@@ -877,7 +877,7 @@ ${recent.map((h, n) => `[${n + 1}] ${h.metadata.docidentifier ?? ""} \xA7${h.met
877
877
  telemetry(env, ctx, "member", "research", MODELS.research, false, 0, queryHash, q.lang);
878
878
  throw new Error("The generation model is unavailable; please retry.");
879
879
  }
880
- answer = canonicalRefusal(answer, { cited: usedHits.length > 0, conversational: false });
880
+ answer = canonicalRefusal(answer, { cited: /\[[^\]]+\]/.test(answer), conversational: false });
881
881
  const anchors = checkQuoteAnchors(answer, used.map((h) => h.text));
882
882
  if (anchors.violations.length) console.log("research anchors:", anchors.violations.length, "unverified");
883
883
  const out = {
@@ -1052,7 +1052,7 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
1052
1052
  const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
1053
1053
  payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
1054
1054
  } else {
1055
- const res = name === "ask" ? await (await import("../../ask-PFU74MIP.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-NIRCPRMW.js")).handleSearch(env, ctx, inner, tier, key);
1055
+ const res = name === "ask" ? await (await import("../../ask-N5I35VCK.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LFS5HHMZ.js")).handleSearch(env, ctx, inner, tier, key);
1056
1056
  payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
1057
1057
  }
1058
1058
  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,10 +1,12 @@
1
1
  import {
2
+ cacheableAnswer,
2
3
  canonicalRefusal,
3
4
  exciseMisplacedPin,
4
5
  refusalAnswer
5
- } from "../../chunk-WPOKSNPE.js";
6
+ } from "../../chunk-XBZIJMS7.js";
6
7
  import "../../chunk-3FYJM7LH.js";
7
8
  export {
9
+ cacheableAnswer,
8
10
  canonicalRefusal,
9
11
  exciseMisplacedPin,
10
12
  refusalAnswer
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.134",
30
+ "version": "0.2.136",
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 { canonicalRefusal, exciseMisplacedPin } from "./refusal";
20
+ import { cacheableAnswer, 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";
@@ -627,7 +627,12 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
627
627
  let understanding: any = null;
628
628
  // the loose redirect-offer family's gate (refusal.ts): knowledge-seek
629
629
  // questions only, uncited answers only — a conversational turn's
630
- // scope vocabulary is the answer itself
630
+ // scope vocabulary is the answer itself. "Cited" is the ANSWER TEXT
631
+ // carrying citation markers, NEVER the prompt's hit count: retrieval
632
+ // always returns passages (the model just declines to cite them on a
633
+ // refusal) — gating on usedHits left every refusal "cited" and the
634
+ // wall dark through three deploys (2026-10-11)
635
+ const answerCites = (t: string) => /\[[^\]]+\]/.test(t);
631
636
  const refusalScope = (cited: boolean) => ({ cited, conversational: understanding?.intent === "conversational" });
632
637
  // a query naming a model node (/req/…, /term/…) is node-SCOPED: its
633
638
  // ── Option C: optimistic parallel retrieval ──
@@ -1541,7 +1546,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1541
1546
  }
1542
1547
  // the misplaced-pin wall rides the streamed path too: a
1543
1548
  // CITED stream is not a refusal
1544
- const canonical0 = usedHits.length > 0 ? exciseMisplacedPin(canonicalRefusal(full, refusalScope(usedHits.length > 0)), true) : canonicalRefusal(full, refusalScope(false));
1549
+ const canonical0 = usedHits.length > 0 ? exciseMisplacedPin(canonicalRefusal(full, refusalScope(answerCites(full))), true) : canonicalRefusal(full, refusalScope(false));
1545
1550
  // answer contract v2: validate [[u:]] refs, resolve typed blocks
1546
1551
  const c2 = canonical0.includes(refusalAnswer())
1547
1552
  ? { text: canonical0, blocks: [], dropped: [] as string[] }
@@ -1582,7 +1587,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1582
1587
  if (streamed.violations.length > 0) {
1583
1588
  console.log("anchors:", streamed.violations.length, "of", streamed.total, "unverified — not caching");
1584
1589
  }
1585
- if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && !canonical.includes(refusalAnswer()) && !ablate) {
1590
+ if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && cacheableAnswer(canonical, refusalScope(answerCites(canonical))) && !ablate) {
1586
1591
  ctx.waitUntil(
1587
1592
  env.CACHE.put(exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt))), JSON.stringify({ answer: canonical, citations: cites, model, query_hash: queryHash, ...(sourceQuality ? { source_quality: sourceQuality, confidence_note: streamedNote || qualityNote(sourceQuality) } : {}), ...(streamedEntailment ? { entailment: streamedEntailment } : {}) }), { expirationTtl: LIMITS.cacheTtlSec }),
1588
1593
  );
@@ -1645,7 +1650,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1645
1650
  );
1646
1651
  answer = await generateOnce(env, MODELS.fallback, flat, routeEffort(routed.route));
1647
1652
  }
1648
- if (answer) answer = canonicalRefusal(answer, refusalScope(usedHits.length > 0));
1653
+ if (answer) answer = canonicalRefusal(answer, refusalScope(answerCites(answer)));
1649
1654
  // the misplaced-pin wall: a CITED answer is not a refusal — excise
1650
1655
  // the pinned sentence if the model dropped it in as a disclaimer
1651
1656
  // (measured on chip-scoped asks: correct cited answers carrying the
@@ -1688,7 +1693,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1688
1693
  generateRetries += 1;
1689
1694
  const corrected = await generateOnce(env, model, [...messages, { role: "system", content: note }], routeEffort(routed.route));
1690
1695
  if (corrected) {
1691
- const correctedAnswer = canonicalRefusal(corrected, refusalScope(usedHits.length > 0));
1696
+ const correctedAnswer = canonicalRefusal(corrected, refusalScope(answerCites(corrected)));
1692
1697
  const retryAnchors = checkQuoteAnchors(correctedAnswer, used.map((h: Hit) => h.text));
1693
1698
  const retryRetyped = tableRetyped(correctedAnswer, hasTableUnit);
1694
1699
  if (retryAnchors.violations.length < anchors.violations.length || (!retryRetyped && retyped) || (unreferenced && correctedAnswer.includes("[[u:"))) {
@@ -1733,7 +1738,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1733
1738
  const retryAnswer = await generateOnce(env, model, retryMessages, routeEffort(routed.route));
1734
1739
  // the answer now comes from the retry passages — citations must follow
1735
1740
  if (retryAnswer) {
1736
- answer = canonicalRefusal(retryAnswer, refusalScope(retryRetrieve.hits.length > 0));
1741
+ answer = canonicalRefusal(retryAnswer, refusalScope(answerCites(retryAnswer)));
1737
1742
  used = retryUsed;
1738
1743
  }
1739
1744
  }
@@ -1785,7 +1790,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1785
1790
  messages[messages.length - 1],
1786
1791
  ], effort);
1787
1792
  if (regen && !regen.includes(refusalAnswer())) {
1788
- answer = canonicalRefusal(regen, refusalScope(true));
1793
+ answer = canonicalRefusal(regen, refusalScope(answerCites(regen)));
1789
1794
  console.log("blind-answer: regeneration grounded");
1790
1795
  }
1791
1796
  }
@@ -1811,7 +1816,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1811
1816
  }
1812
1817
  }
1813
1818
  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 } };
1814
- const cacheable = !contextual && !declaredCtx && !ablate && !answer.includes(refusalAnswer()) && finalAnchors.violations.length === 0;
1819
+ const cacheable = !contextual && !declaredCtx && !ablate && cacheableAnswer(answer, refusalScope(answerCites(answer))) && finalAnchors.violations.length === 0;
1815
1820
  if (cacheable) {
1816
1821
  const ck = exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt)));
1817
1822
  ctx.waitUntil(env.CACHE.put(ck, JSON.stringify(out), { expirationTtl: LIMITS.cacheTtlSec }));
@@ -146,3 +146,15 @@ export function exciseMisplacedPin(answer: string, cites: boolean): string {
146
146
  }
147
147
  return out.replace(/^[ \t\n]+/, "").replace(/\n{3,}/g, "\n\n").trim();
148
148
  }
149
+
150
+ /** Cacheable under the refusal law (2026-10-11, the poisoned-entry
151
+ * lesson): "refusals are never cached" was enforced as "answers
152
+ * containing the pin are never cached" — backwards for DRIFT, which
153
+ * lacks the pin and cached itself across deploy windows (an edge
154
+ * instance running pre-wall code wrote a drifted refusal that served
155
+ * every later ask). An answer is cacheable only when it carries no
156
+ * pin AND the wall would not rewrite it. */
157
+ export function cacheableAnswer(answer: string, scope?: RefusalScope): boolean {
158
+ if (answer.includes(refusalAnswer())) return false;
159
+ return canonicalRefusal(answer, scope) === answer;
160
+ }
@@ -164,7 +164,7 @@ async function run(
164
164
  telemetry(env, ctx, "member", "research", MODELS.research, false, 0, queryHash, q.lang);
165
165
  throw new Error("The generation model is unavailable; please retry.");
166
166
  }
167
- answer = canonicalRefusal(answer, { cited: usedHits.length > 0, conversational: false });
167
+ answer = canonicalRefusal(answer, { cited: /\[[^\]]+\]/.test(answer), conversational: false });
168
168
  const anchors = checkQuoteAnchors(answer, used.map((h: Hit) => h.text));
169
169
  if (anchors.violations.length) console.log("research anchors:", anchors.violations.length, "unverified");
170
170
  const out = {