@konneal/engine 0.2.135 → 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,6 +1,6 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-27NZR4HF.js";
3
+ } from "./chunk-YLBPABDQ.js";
4
4
  import "./chunk-J6LBR6GI.js";
5
5
  import "./chunk-7AXXR7GD.js";
6
6
  import "./chunk-TH5XGPD5.js";
@@ -1981,6 +1981,7 @@ async function handleAsk(env, ctx, req, tier, key) {
1981
1981
  }
1982
1982
  }
1983
1983
  let understanding = null;
1984
+ const answerCites = (t) => /\[[^\]]+\]/.test(t);
1984
1985
  const refusalScope = (cited) => ({ cited, conversational: understanding?.intent === "conversational" });
1985
1986
  let optimisticVec = null;
1986
1987
  let optimisticHits = [];
@@ -2622,7 +2623,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2622
2623
  }
2623
2624
  }
2624
2625
  }
2625
- 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));
2626
2627
  const c2 = canonical0.includes(refusalAnswer()) ? { text: canonical0, blocks: [], dropped: [] } : await contractV2(env.DB, canonical0, usedHits);
2627
2628
  send({
2628
2629
  type: "done",
@@ -2660,7 +2661,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2660
2661
  if (streamed.violations.length > 0) {
2661
2662
  console.log("anchors:", streamed.violations.length, "of", streamed.total, "unverified \u2014 not caching");
2662
2663
  }
2663
- if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && cacheableAnswer(canonical, refusalScope(usedHits.length > 0)) && !ablate) {
2664
+ if (streamed.violations.length === 0 && canonical.length > 0 && !contextual && !declaredCtx && cacheableAnswer(canonical, refusalScope(answerCites(canonical))) && !ablate) {
2664
2665
  ctx.waitUntil(
2665
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 })
2666
2667
  );
@@ -2706,7 +2707,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2706
2707
  );
2707
2708
  answer = await generateOnce(env, MODELS.fallback, flat, routeEffort(routed.route));
2708
2709
  }
2709
- if (answer) answer = canonicalRefusal(answer, refusalScope(usedHits.length > 0));
2710
+ if (answer) answer = canonicalRefusal(answer, refusalScope(answerCites(answer)));
2710
2711
  if (answer && usedHits.length > 0) answer = exciseMisplacedPin(answer, true);
2711
2712
  stageTiming.generate = Date.now() - tGen;
2712
2713
  let used = usedHits;
@@ -2731,7 +2732,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2731
2732
  generateRetries += 1;
2732
2733
  const corrected = await generateOnce(env, model, [...messages, { role: "system", content: note }], routeEffort(routed.route));
2733
2734
  if (corrected) {
2734
- const correctedAnswer = canonicalRefusal(corrected, refusalScope(usedHits.length > 0));
2735
+ const correctedAnswer = canonicalRefusal(corrected, refusalScope(answerCites(corrected)));
2735
2736
  const retryAnchors = checkQuoteAnchors(correctedAnswer, used.map((h) => h.text));
2736
2737
  const retryRetyped = tableRetyped(correctedAnswer, hasTableUnit);
2737
2738
  if (retryAnchors.violations.length < anchors.violations.length || !retryRetyped && retyped || unreferenced && correctedAnswer.includes("[[u:")) {
@@ -2756,7 +2757,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2756
2757
  const { messages: retryMessages, usedHits: retryUsed } = buildMessages(q.query, retryRetrieve.hits, q.lang, keptHistory, void 0, summary, budget);
2757
2758
  const retryAnswer = await generateOnce(env, model, retryMessages, routeEffort(routed.route));
2758
2759
  if (retryAnswer) {
2759
- answer = canonicalRefusal(retryAnswer, refusalScope(retryRetrieve.hits.length > 0));
2760
+ answer = canonicalRefusal(retryAnswer, refusalScope(answerCites(retryAnswer)));
2760
2761
  used = retryUsed;
2761
2762
  }
2762
2763
  }
@@ -2788,7 +2789,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2788
2789
  messages[messages.length - 1]
2789
2790
  ], effort);
2790
2791
  if (regen && !regen.includes(refusalAnswer())) {
2791
- answer = canonicalRefusal(regen, refusalScope(true));
2792
+ answer = canonicalRefusal(regen, refusalScope(answerCites(regen)));
2792
2793
  console.log("blind-answer: regeneration grounded");
2793
2794
  }
2794
2795
  }
@@ -2808,7 +2809,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2808
2809
  }
2809
2810
  }
2810
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 } };
2811
- const cacheable = !contextual && !declaredCtx && !ablate && cacheableAnswer(answer, refusalScope(usedHits.length > 0)) && finalAnchors.violations.length === 0;
2812
+ const cacheable = !contextual && !declaredCtx && !ablate && cacheableAnswer(answer, refusalScope(answerCites(answer))) && finalAnchors.violations.length === 0;
2812
2813
  if (cacheable) {
2813
2814
  const ck = exactCacheKey(env.INDEX_VERSION, gen, ns, await sha256Hex(cacheKeyMaterial(q.query, q.lang, salt)));
2814
2815
  ctx.waitUntil(env.CACHE.put(ck, JSON.stringify(out), { expirationTtl: LIMITS.cacheTtlSec }));
@@ -4,7 +4,7 @@ import {
4
4
  handleMemories,
5
5
  scoreFaithfulness,
6
6
  scoreJudge
7
- } from "../../chunk-27NZR4HF.js";
7
+ } from "../../chunk-YLBPABDQ.js";
8
8
  import {
9
9
  TOOLS_REGISTRY,
10
10
  bindModelNode,
@@ -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-JX55WMFR.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LFS5HHMZ.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 });
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.135",
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",
@@ -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 && cacheableAnswer(canonical, refusalScope(usedHits.length > 0)) && !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 && cacheableAnswer(answer, refusalScope(usedHits.length > 0)) && 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 }));
@@ -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 = {