@konneal/engine 0.2.146 → 0.2.148
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-RIWVOI3D.js → ask-MBKT7D7M.js} +1 -1
- package/dist/{chunk-VISPFCQP.js → chunk-2SRQDX7Y.js} +38 -5
- package/dist/worker_public/src/index.js +2 -2
- package/docs/INGEST-ARCHITECTURE.md +2 -2
- package/docs/MKO-EXPORT-PIPELINE.md +16 -0
- package/docs/knowledge-annealment.md +8 -4
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +62 -5
|
@@ -1638,6 +1638,10 @@ async function extractNameplate(ai, model, image) {
|
|
|
1638
1638
|
}
|
|
1639
1639
|
|
|
1640
1640
|
// workers/worker_public/src/ask.ts
|
|
1641
|
+
function toolNarration(t) {
|
|
1642
|
+
return /^(?:the\s+)?[a-z][a-z_.]*\.[a-z_]+ tool returned\b/i.test(t.slice(0, 200)) || /tool returned,? for the query\b/i.test(t.slice(0, 300));
|
|
1643
|
+
}
|
|
1644
|
+
var TOOL_NARRATION_NOTICE = "Correction notice: your draft narrated an internal tool call ('the \u2026 tool returned \u2026') instead of answering. Rewrite: answer the question directly from the retrieved passages, cite the clauses, and never mention tools, queries, wire formats, or JSON.";
|
|
1641
1645
|
function userImageDataUrl(body) {
|
|
1642
1646
|
const img = body?.image;
|
|
1643
1647
|
if (img == null) return null;
|
|
@@ -2607,9 +2611,38 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2607
2611
|
const stats = {};
|
|
2608
2612
|
let full = "";
|
|
2609
2613
|
try {
|
|
2614
|
+
let headSettled = false;
|
|
2610
2615
|
for await (const tok of sseTokens(stream, stats)) {
|
|
2611
2616
|
full += tok;
|
|
2612
|
-
|
|
2617
|
+
if (headSettled) {
|
|
2618
|
+
send({ type: "token", v: tok });
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2621
|
+
if (full.length < 80) continue;
|
|
2622
|
+
headSettled = true;
|
|
2623
|
+
if (toolNarration(full)) {
|
|
2624
|
+
console.log("tool-narration(stream): regenerating once");
|
|
2625
|
+
send({ type: "retry" });
|
|
2626
|
+
const regen = await generateStream(env, model, [
|
|
2627
|
+
...messages,
|
|
2628
|
+
{ role: "system", content: TOOL_NARRATION_NOTICE }
|
|
2629
|
+
], routeEffort(routed.route));
|
|
2630
|
+
if (regen) {
|
|
2631
|
+
full = "";
|
|
2632
|
+
for await (const tok2 of sseTokens(regen, stats)) {
|
|
2633
|
+
full += tok2;
|
|
2634
|
+
send({ type: "token", v: tok2 });
|
|
2635
|
+
}
|
|
2636
|
+
} else {
|
|
2637
|
+
send({ type: "token", v: full });
|
|
2638
|
+
}
|
|
2639
|
+
break;
|
|
2640
|
+
}
|
|
2641
|
+
send({ type: "token", v: full });
|
|
2642
|
+
}
|
|
2643
|
+
if (!headSettled) {
|
|
2644
|
+
headSettled = true;
|
|
2645
|
+
if (!toolNarration(full)) send({ type: "token", v: full });
|
|
2613
2646
|
}
|
|
2614
2647
|
} catch {
|
|
2615
2648
|
}
|
|
@@ -2740,17 +2773,17 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2740
2773
|
);
|
|
2741
2774
|
return nums.filter((n) => tableNums.has(n)).length >= 2;
|
|
2742
2775
|
})();
|
|
2743
|
-
if (anchors.violations.length > 0 || retyped || unreferenced) {
|
|
2744
|
-
console.log("contract check:", anchors.violations.length, "anchor violations; tableRetyped:", retyped, "; tableDataUnreferenced:", unreferenced, "\u2014 regenerating");
|
|
2776
|
+
if (anchors.violations.length > 0 || retyped || unreferenced || toolNarration(answer)) {
|
|
2777
|
+
console.log("contract check:", anchors.violations.length, "anchor violations; tableRetyped:", retyped, "; tableDataUnreferenced:", unreferenced, "; toolNarration:", toolNarration(answer), "\u2014 regenerating");
|
|
2745
2778
|
const tableUnitId = unreferenced ? used.find((h) => h.metadata.unit_id && h.metadata.block === "table")?.metadata.unit_id : void 0;
|
|
2746
|
-
const note = retyped || unreferenced ? `Correction notice: your draft reproduced a table as markdown or presented a served table's data without its reference. Rewrite the answer: describe the table in prose, cite the clause, and write the reference token [[u:${tableUnitId ?? "<unit id>"}]] exactly where the table belongs. Do not render any table as markdown.` : ANCHOR_CORRECTION_NOTE;
|
|
2779
|
+
const note = toolNarration(answer) ? TOOL_NARRATION_NOTICE : retyped || unreferenced ? `Correction notice: your draft reproduced a table as markdown or presented a served table's data without its reference. Rewrite the answer: describe the table in prose, cite the clause, and write the reference token [[u:${tableUnitId ?? "<unit id>"}]] exactly where the table belongs. Do not render any table as markdown.` : ANCHOR_CORRECTION_NOTE;
|
|
2747
2780
|
generateRetries += 1;
|
|
2748
2781
|
const corrected = await generateOnce(env, model, [...messages, { role: "system", content: note }], routeEffort(routed.route));
|
|
2749
2782
|
if (corrected) {
|
|
2750
2783
|
const correctedAnswer = canonicalRefusal(corrected, refusalScope(answerCites(corrected)));
|
|
2751
2784
|
const retryAnchors = checkQuoteAnchors(correctedAnswer, used.map((h) => h.text));
|
|
2752
2785
|
const retryRetyped = tableRetyped(correctedAnswer, hasTableUnit);
|
|
2753
|
-
if (retryAnchors.violations.length < anchors.violations.length || !retryRetyped && retyped || unreferenced && correctedAnswer.includes("[[u:")) {
|
|
2786
|
+
if (retryAnchors.violations.length < anchors.violations.length || !retryRetyped && retyped || unreferenced && correctedAnswer.includes("[[u:") || !toolNarration(correctedAnswer)) {
|
|
2754
2787
|
answer = correctedAnswer;
|
|
2755
2788
|
}
|
|
2756
2789
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
handleMemories,
|
|
5
5
|
scoreFaithfulness,
|
|
6
6
|
scoreJudge
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-2SRQDX7Y.js";
|
|
8
8
|
import "../../chunk-ECOYO7EX.js";
|
|
9
9
|
import {
|
|
10
10
|
TOOLS_REGISTRY,
|
|
@@ -1079,7 +1079,7 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
|
|
|
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-MBKT7D7M.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 });
|
|
@@ -80,9 +80,9 @@ hand.
|
|
|
80
80
|
|---|---|
|
|
81
81
|
| OII | de-facto: parse.py (HTML-first) + graph.py (relaton/glossarist). Target: consume the four native object serializations; the AI-serialization proposal (metanorma#592) is the upstream contract |
|
|
82
82
|
| Identity SSOT | ✅ documents registry in D1 (derived status, active flags, supersession chains) |
|
|
83
|
-
| Registry serving | ✅ edition notes injected into doc-scoped asks; `
|
|
83
|
+
| Registry serving | ✅ edition notes injected into doc-scoped asks; the registry serves through the MCP `oiml_documents` tool and the `/docs/*` renderings |
|
|
84
84
|
| Graph projection | ✅ 7,128 nodes / 6,486 edges in D1; query lane live (defined_terms → defines → candidates) |
|
|
85
|
-
| Chunking |
|
|
85
|
+
| Chunking | typed units live on the MKO plane (tables/formulas/terms/requirements as typed payloads, clause-anchored citations); the parse plane stays clause-prose |
|
|
86
86
|
| Enrichment | ✅ 98.7% corpus (the residual ~400 are persistent empty enrichments), KV-cached, replayed from the durable record after full restores |
|
|
87
87
|
| Verification | unit suites + contract tests in CI (`npm run test:units`, pydantic↔TS wire contract, schema-union drift); the data-quality report loop = next build |
|
|
88
88
|
| Orchestration | manual commands today; one-command pipeline (`ingest run --doc X`) when the above land |
|
|
@@ -84,6 +84,22 @@ All **37** compiled sample documents (R/D/B/G/E + OIML-CS admin + parts
|
|
|
84
84
|
342 cited documents (native Relaton + pubid), ~11k graph rows
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
## Full-corpus run (2026-10-13, live; the lutaml-0.8.106 + ± re-baseline)
|
|
88
|
+
|
|
89
|
+
**46** documents export and ingest at the current stack (lutaml-model
|
|
90
|
+
0.8.106 float, the ± glyph mapping in plain-text extraction, the six
|
|
91
|
+
series-less editions carrying `:docidentifier:`):
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
46/46 bundles → 5,336 chunks, 1,423 typed units, 293 glossary terms,
|
|
95
|
+
425 cited documents; D1: 15,390-statement graph projection, 33,480 FTS rows
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Uncertainty ranges are first-class end to end on this baseline: display
|
|
99
|
+
text carries ± (AsciiMath `pm` maps at the glyph layer), tolerance cells
|
|
100
|
+
parse into `payload.uncertainties[]` records, and the verdict engine
|
|
101
|
+
evaluates stated bands with guarded acceptance.
|
|
102
|
+
|
|
87
103
|
Includes the amendment cases: R 60/A1 keeps its 17 terms in an annex
|
|
88
104
|
(recovered into the glossary), and the OIML-CS admin documents keep
|
|
89
105
|
their whole body in `<preface>` (walked as clauses). Clause text now
|
|
@@ -262,10 +262,14 @@ Beyond the ladder, the machine objects are EXECUTED, not retrieved:
|
|
|
262
262
|
machine limit gets the check evaluated against its stated values —
|
|
263
263
|
pass / the standard's own violation word / void naming what is
|
|
264
264
|
missing — attached as data the answer must present faithfully.
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
265
|
+
Stated tolerances bind beside their values and the check re-runs at
|
|
266
|
+
every band corner; a band spanning both outcomes voids with its
|
|
267
|
+
reason (guarded acceptance). Counterfactuals are free (values are
|
|
268
|
+
values).
|
|
269
|
+
- **Honest absence**: the question names a standard, the model plane
|
|
270
|
+
is enumerated, and a fruitless search answers as a plain sentence
|
|
271
|
+
with the scope named — never a bare refusal, never a certificate
|
|
272
|
+
document (the `/v1/absence` endpoint retired at engine 0.2.54).
|
|
269
273
|
- **Answer verification** (`/v1/verify`): the deterministic contract
|
|
270
274
|
battery plus a judged faithfulness score, exposed for any answer.
|
|
271
275
|
|
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.148",
|
|
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",
|
|
@@ -50,6 +50,21 @@ import { operationsLane } from "./operations.ts";
|
|
|
50
50
|
import { extractNameplate, nameplateRegisterQuery } from "./nameplate.ts";
|
|
51
51
|
import { P } from "./profile.ts";
|
|
52
52
|
|
|
53
|
+
// the tool-narration leak (2026-10-14, the R 134-1 probe): the answer
|
|
54
|
+
// model sometimes NARRATES the agent loop's call in prose — 'The
|
|
55
|
+
// db.find tool returned, for the query "…": {…json…}' — instead of
|
|
56
|
+
// answering. The TOOL-line scrub cannot catch prose. The JSON lane
|
|
57
|
+
// regenerates through the contract retry; the streamed lane buffers
|
|
58
|
+
// the head and sends the retry sentinel (the blind-answer guard's
|
|
59
|
+
// client contract) before streaming the fresh answer. One bounded
|
|
60
|
+
// restart per lane.
|
|
61
|
+
function toolNarration(t: string): boolean {
|
|
62
|
+
return /^(?:the\s+)?[a-z][a-z_.]*\.[a-z_]+ tool returned\b/i.test(t.slice(0, 200)) ||
|
|
63
|
+
/tool returned,? for the query\b/i.test(t.slice(0, 300));
|
|
64
|
+
}
|
|
65
|
+
const TOOL_NARRATION_NOTICE =
|
|
66
|
+
"Correction notice: your draft narrated an internal tool call ('the … tool returned …') instead of answering. Rewrite: answer the question directly from the retrieved passages, cite the clauses, and never mention tools, queries, wire formats, or JSON.";
|
|
67
|
+
|
|
53
68
|
/** User-uploaded image for multimodal questions: a data URL
|
|
54
69
|
* (data:image/(png|jpeg|webp|gif);base64,…) up to 6 MB of payload. The
|
|
55
70
|
* question text still drives retrieval; the image is CONTEXT for the
|
|
@@ -1536,9 +1551,43 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1536
1551
|
const stats: { usage?: unknown } = {};
|
|
1537
1552
|
let full = "";
|
|
1538
1553
|
try {
|
|
1554
|
+
// the head buffers until it can rule out the tool-narration
|
|
1555
|
+
// leak (the JSON lane's contract retry cannot run here — the
|
|
1556
|
+
// tokens are already on the wire): a narrating head sends the
|
|
1557
|
+
// retry sentinel — the blind-answer guard's client contract,
|
|
1558
|
+
// which clears the buffer — and the fresh answer streams.
|
|
1559
|
+
// One bounded restart; a narrating retry ships as-is.
|
|
1560
|
+
let headSettled = false;
|
|
1539
1561
|
for await (const tok of sseTokens(stream, stats)) {
|
|
1540
1562
|
full += tok;
|
|
1541
|
-
send({ type: "token", v: tok });
|
|
1563
|
+
if (headSettled) { send({ type: "token", v: tok }); continue; }
|
|
1564
|
+
if (full.length < 80) continue;
|
|
1565
|
+
headSettled = true;
|
|
1566
|
+
if (toolNarration(full)) {
|
|
1567
|
+
console.log("tool-narration(stream): regenerating once");
|
|
1568
|
+
send({ type: "retry" });
|
|
1569
|
+
const regen = await generateStream(env, model, [
|
|
1570
|
+
...messages,
|
|
1571
|
+
{ role: "system", content: TOOL_NARRATION_NOTICE },
|
|
1572
|
+
], routeEffort(routed.route));
|
|
1573
|
+
if (regen) {
|
|
1574
|
+
full = "";
|
|
1575
|
+
for await (const tok2 of sseTokens(regen, stats)) {
|
|
1576
|
+
full += tok2;
|
|
1577
|
+
send({ type: "token", v: tok2 });
|
|
1578
|
+
}
|
|
1579
|
+
} else {
|
|
1580
|
+
// the restart failed — ship the buffered head rather
|
|
1581
|
+
// than an empty answer
|
|
1582
|
+
send({ type: "token", v: full });
|
|
1583
|
+
}
|
|
1584
|
+
break;
|
|
1585
|
+
}
|
|
1586
|
+
send({ type: "token", v: full });
|
|
1587
|
+
}
|
|
1588
|
+
if (!headSettled) {
|
|
1589
|
+
headSettled = true;
|
|
1590
|
+
if (!toolNarration(full)) send({ type: "token", v: full });
|
|
1542
1591
|
}
|
|
1543
1592
|
} catch {
|
|
1544
1593
|
// stream ended prematurely — deliver what we have
|
|
@@ -1690,6 +1739,12 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1690
1739
|
// (measured on chip-scoped asks: correct cited answers carrying the
|
|
1691
1740
|
// pin mid-text, read by users and graders as total refusals)
|
|
1692
1741
|
if (answer) answer = exciseMisplacedPin(answer, answerCites(answer));
|
|
1742
|
+
// the tool-narration leak (2026-10-14, the R 134-1 probe): the answer
|
|
1743
|
+
// model sometimes NARRATES the agent loop's call in prose — 'The
|
|
1744
|
+
// db.find tool returned, for the query "…": {…json…}' — instead of
|
|
1745
|
+
// answering. The line scrub cannot catch prose; the contract retry
|
|
1746
|
+
// below regenerates with an explicit notice, and the detector also
|
|
1747
|
+
// gates the retry's acceptance so a narrating retry cannot ship.
|
|
1693
1748
|
stageTiming.generate = Date.now() - tGen;
|
|
1694
1749
|
|
|
1695
1750
|
// ── Deterministic quote-anchor + table-retyping check ──
|
|
@@ -1714,14 +1769,16 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1714
1769
|
);
|
|
1715
1770
|
return nums.filter((n) => tableNums.has(n)).length >= 2;
|
|
1716
1771
|
})();
|
|
1717
|
-
if (anchors.violations.length > 0 || retyped || unreferenced) {
|
|
1718
|
-
console.log("contract check:", anchors.violations.length, "anchor violations; tableRetyped:", retyped, "; tableDataUnreferenced:", unreferenced, "— regenerating");
|
|
1772
|
+
if (anchors.violations.length > 0 || retyped || unreferenced || toolNarration(answer)) {
|
|
1773
|
+
console.log("contract check:", anchors.violations.length, "anchor violations; tableRetyped:", retyped, "; tableDataUnreferenced:", unreferenced, "; toolNarration:", toolNarration(answer), "— regenerating");
|
|
1719
1774
|
// name the EXACT unit the token must reference — a generic note
|
|
1720
1775
|
// leaves the model guessing which id to write
|
|
1721
1776
|
const tableUnitId = unreferenced
|
|
1722
1777
|
? used.find((h: Hit) => h.metadata.unit_id && h.metadata.block === "table")?.metadata.unit_id
|
|
1723
1778
|
: undefined;
|
|
1724
|
-
const note =
|
|
1779
|
+
const note = toolNarration(answer)
|
|
1780
|
+
? TOOL_NARRATION_NOTICE
|
|
1781
|
+
: retyped || unreferenced
|
|
1725
1782
|
? `Correction notice: your draft reproduced a table as markdown or presented a served table's data without its reference. Rewrite the answer: describe the table in prose, cite the clause, and write the reference token [[u:${tableUnitId ?? "<unit id>"}]] exactly where the table belongs. Do not render any table as markdown.`
|
|
1726
1783
|
: ANCHOR_CORRECTION_NOTE;
|
|
1727
1784
|
generateRetries += 1;
|
|
@@ -1730,7 +1787,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1730
1787
|
const correctedAnswer = canonicalRefusal(corrected, refusalScope(answerCites(corrected)));
|
|
1731
1788
|
const retryAnchors = checkQuoteAnchors(correctedAnswer, used.map((h: Hit) => h.text));
|
|
1732
1789
|
const retryRetyped = tableRetyped(correctedAnswer, hasTableUnit);
|
|
1733
|
-
if (retryAnchors.violations.length < anchors.violations.length || (!retryRetyped && retyped) || (unreferenced && correctedAnswer.includes("[[u:"))) {
|
|
1790
|
+
if (retryAnchors.violations.length < anchors.violations.length || (!retryRetyped && retyped) || (unreferenced && correctedAnswer.includes("[[u:")) || !toolNarration(correctedAnswer)) {
|
|
1734
1791
|
answer = correctedAnswer;
|
|
1735
1792
|
}
|
|
1736
1793
|
}
|