@konneal/engine 0.2.17 → 0.2.19
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-PKKNMLLM.js → ask-HCZ4CDUR.js} +1 -1
- package/dist/boundary.d.ts +17 -0
- package/dist/{chunk-DRQ37UGZ.js → chunk-AN4VFBBZ.js} +84 -9
- package/dist/worker_public/src/index.js +2 -2
- package/package.json +1 -1
- package/workers/worker_public/src/aggregation.ts +8 -2
- package/workers/worker_public/src/ask.ts +43 -11
- package/workers/worker_public/src/boundary.ts +69 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface LicensedEntry {
|
|
2
|
+
key: string;
|
|
3
|
+
package?: string;
|
|
4
|
+
doc_number?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LicensedMatch {
|
|
8
|
+
entry: LicensedEntry;
|
|
9
|
+
matched: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function distinctiveTokens(title: string): string[];
|
|
12
|
+
/** The licensed entry whose distinctive title tokens best match the
|
|
13
|
+
* question. Needs TWO token hits (or one hyphenated-compound hit) —
|
|
14
|
+
* a single shared word is not a topic match. */
|
|
15
|
+
export declare function matchLicensedTopic(query: string, licensed: LicensedEntry[]): LicensedMatch | null;
|
|
16
|
+
/** The note text: the posture instruction for an unentitled match. */
|
|
17
|
+
export declare function boundaryNoteText(match: LicensedMatch, citing: string[]): string;
|
|
@@ -960,7 +960,7 @@ function pickTable(nodes, query) {
|
|
|
960
960
|
for (const t of tokens(n.node_id.replace("/table/", ""))) {
|
|
961
961
|
if (t.length >= 3 && queryLower.includes(t)) score += 2;
|
|
962
962
|
}
|
|
963
|
-
for (const w of String(c.name ?? "").toLowerCase().split(/[^a-z0-9.]+/)) {
|
|
963
|
+
for (const w of String(c.name ?? c.definition ?? "").toLowerCase().split(/[^a-z0-9.]+/)) {
|
|
964
964
|
if (w.length >= 4 && queryLower.includes(w)) score += 1;
|
|
965
965
|
}
|
|
966
966
|
if (intervalPairs(cols).some((p) => p.low.unit && stated.has(p.low.unit))) score += 3;
|
|
@@ -981,7 +981,9 @@ function evaluateAggregation(nodes, query) {
|
|
|
981
981
|
const cols = Array.isArray(payload.columns) ? payload.columns : [];
|
|
982
982
|
const rows = (Array.isArray(payload.rows) ? payload.rows : []).filter((r) => Array.isArray(r));
|
|
983
983
|
if (!cols.length || !rows.length) return null;
|
|
984
|
-
const
|
|
984
|
+
const rawTitle = String(content.name ?? content.definition ?? node.node_id.replace("/table/", ""));
|
|
985
|
+
const cut = rawTitle.indexOf(" (");
|
|
986
|
+
const tableTitle = cut > 0 ? rawTitle.slice(0, cut) : rawTitle.slice(0, 120);
|
|
985
987
|
const cite = (what) => `COMPUTED (${operation}) \u2014 ${what}, read from the typed table "${tableTitle}" (${node.node_id}). Present this result and cite the table's clause; the value is machine-computed from the table payload, do not recompute or round it differently.`;
|
|
986
988
|
if (operation === "count") {
|
|
987
989
|
const cc2 = classColumn(cols);
|
|
@@ -1072,6 +1074,54 @@ function evaluateAggregation(nodes, query) {
|
|
|
1072
1074
|
return null;
|
|
1073
1075
|
}
|
|
1074
1076
|
|
|
1077
|
+
// workers/worker_public/src/boundary.ts
|
|
1078
|
+
var TITLE_STOPWORDS = /* @__PURE__ */ new Set([
|
|
1079
|
+
"environmental",
|
|
1080
|
+
"testing",
|
|
1081
|
+
"test",
|
|
1082
|
+
"tests",
|
|
1083
|
+
"guidance",
|
|
1084
|
+
"generic",
|
|
1085
|
+
"standards",
|
|
1086
|
+
"standard",
|
|
1087
|
+
"electromagnetic",
|
|
1088
|
+
"compatibility",
|
|
1089
|
+
"environment",
|
|
1090
|
+
"description",
|
|
1091
|
+
"measurement",
|
|
1092
|
+
"techniques",
|
|
1093
|
+
"immunity",
|
|
1094
|
+
"residential",
|
|
1095
|
+
"commercial",
|
|
1096
|
+
"industrial",
|
|
1097
|
+
"environments",
|
|
1098
|
+
"and",
|
|
1099
|
+
"for",
|
|
1100
|
+
"the",
|
|
1101
|
+
"iec",
|
|
1102
|
+
"iso"
|
|
1103
|
+
]);
|
|
1104
|
+
function distinctiveTokens(title) {
|
|
1105
|
+
return title.toLowerCase().split(/[^a-z0-9.]+/).filter((w) => w.length >= 3 && !TITLE_STOPWORDS.has(w) && !/^[0-9:-]+$/.test(w));
|
|
1106
|
+
}
|
|
1107
|
+
function matchLicensedTopic(query, licensed) {
|
|
1108
|
+
const words = new Set(query.toLowerCase().split(/[^a-z0-9.]+/).filter(Boolean));
|
|
1109
|
+
let best = null;
|
|
1110
|
+
for (const entry of licensed) {
|
|
1111
|
+
if (!entry.title) continue;
|
|
1112
|
+
const matched = distinctiveTokens(entry.title).filter((w) => words.has(w));
|
|
1113
|
+
if (matched.length < 2) continue;
|
|
1114
|
+
if (!best || matched.length > best.matched.length) best = { entry, matched };
|
|
1115
|
+
}
|
|
1116
|
+
return best;
|
|
1117
|
+
}
|
|
1118
|
+
function boundaryNoteText(match, citing) {
|
|
1119
|
+
const doc = match.entry.doc_number ?? match.entry.key;
|
|
1120
|
+
const title = match.entry.title ?? doc;
|
|
1121
|
+
const refs = citing.length ? `The public corpus references it from ${citing.join(", ")}.` : "";
|
|
1122
|
+
return `LICENSED BOUNDARY \u2014 ${title} (IEC ${doc}) is licensed content in this deployment; its procedure is NOT part of the public corpus you are grounded in. ${refs} When answering: name the licensed document as the authoritative source of the procedure and say it is available to entitled callers; do NOT recite its conditioning or severity parameters (specific temperatures, humidity levels, durations or cycle counts) as if from the source \u2014 describe only what the public grounding passages themselves state, attributed to their own publications.`;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1075
1125
|
// workers/worker_public/src/drafts.ts
|
|
1076
1126
|
var ACT_VERB = "(?:draft|prepare|pre-?fill|fill\\s+(?:in|out)|start|submit|file|lodge)";
|
|
1077
1127
|
var ACT_TARGET = "(?:new\\s+)?(?:certification\\s+|type[ -]evaluation\\s+|OIML[- ]CS\\s+)?application";
|
|
@@ -1864,6 +1914,17 @@ ${summary}` }] : [],
|
|
|
1864
1914
|
const modelNote = boundModel && !boundModel.gated ? modelGroundingBlock(boundModel) : void 0;
|
|
1865
1915
|
const machineVerdict = boundModel && !boundModel.gated ? evaluate(boundModel.content, q.query) : null;
|
|
1866
1916
|
const machineNote = machineVerdict && boundModel ? verdictNote(machineVerdict, boundModel) : void 0;
|
|
1917
|
+
const modelNodeRows = async (kind, docNum) => {
|
|
1918
|
+
const attempt = (num3) => {
|
|
1919
|
+
const sql = num3 ? `SELECT standard, node_id, content FROM model_nodes WHERE kind = '${kind}' AND standard LIKE '%' || ?1` : `SELECT standard, node_id, content FROM model_nodes WHERE kind = '${kind}'`;
|
|
1920
|
+
return num3 ? env.DB.prepare(sql).bind(num3) : env.DB.prepare(sql);
|
|
1921
|
+
};
|
|
1922
|
+
let rows = await attempt(docNum).all().catch(() => ({ results: [] }));
|
|
1923
|
+
if (!(rows.results ?? []).length && docNum && docNum.includes("-")) {
|
|
1924
|
+
rows = await attempt(docNum.replace(/-\d+$/, "")).all().catch(() => ({ results: [] }));
|
|
1925
|
+
}
|
|
1926
|
+
return rows;
|
|
1927
|
+
};
|
|
1867
1928
|
let conditionVerdict = null;
|
|
1868
1929
|
let conditionStandard = null;
|
|
1869
1930
|
if (!machineVerdict && !boundModel && P().publisher.features?.model_plane) {
|
|
@@ -1872,9 +1933,7 @@ ${summary}` }] : [],
|
|
|
1872
1933
|
const stated = quantitiesIn(q.query);
|
|
1873
1934
|
if (severityWord && Object.keys(stated).length >= 1) {
|
|
1874
1935
|
const docNum = modelDocHint?.doc_number;
|
|
1875
|
-
const
|
|
1876
|
-
const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
|
|
1877
|
-
const rows = await stmt.all().catch(() => ({ results: [] }));
|
|
1936
|
+
const rows = await modelNodeRows("condition_set", docNum);
|
|
1878
1937
|
const candidates = (rows.results ?? []).filter((r) => {
|
|
1879
1938
|
const entry = licensedEntryForPackage(String(r.standard));
|
|
1880
1939
|
return !entry || (standardKeys?.has(entry.key) ?? false);
|
|
@@ -1923,9 +1982,7 @@ ${summary}` }] : [],
|
|
|
1923
1982
|
let aggregationStandard = null;
|
|
1924
1983
|
if (!machineVerdict && !boundModel && !conditionVerdict && P().publisher.features?.model_plane) {
|
|
1925
1984
|
const docNum = modelDocHint?.doc_number;
|
|
1926
|
-
const
|
|
1927
|
-
const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
|
|
1928
|
-
const rows = await stmt.all().catch(() => ({ results: [] }));
|
|
1985
|
+
const rows = await modelNodeRows("table", docNum);
|
|
1929
1986
|
const candidates = (rows.results ?? []).filter((r) => {
|
|
1930
1987
|
const entry = licensedEntryForPackage(String(r.standard));
|
|
1931
1988
|
return !entry || (standardKeys?.has(entry.key) ?? false);
|
|
@@ -1954,6 +2011,24 @@ ${summary}` }] : [],
|
|
|
1954
2011
|
}
|
|
1955
2012
|
} : null;
|
|
1956
2013
|
if (aggregationVerdict) console.log("aggregation engine:", aggregationVerdict.operation, aggregationVerdict.table, "\u2192", aggregationVerdict.value);
|
|
2014
|
+
let boundaryNote = null;
|
|
2015
|
+
if (P().sources?.licensed?.length) {
|
|
2016
|
+
const match = matchLicensedTopic(q.query, P().sources.licensed);
|
|
2017
|
+
if (match && !(standardKeys?.has(match.entry.key) ?? false)) {
|
|
2018
|
+
const docNum = match.entry.doc_number ?? "";
|
|
2019
|
+
let citing = [];
|
|
2020
|
+
if (docNum) {
|
|
2021
|
+
const rows = await env.DB.prepare(
|
|
2022
|
+
"SELECT n.label AS label FROM graph_edges e JOIN graph_nodes n ON e.src = n.id WHERE e.kind = 'cites' AND e.dst LIKE ?1 LIMIT 4"
|
|
2023
|
+
).bind(`%${docNum}%`).all().catch(() => ({ results: [] }));
|
|
2024
|
+
citing = (rows.results ?? []).map((r) => {
|
|
2025
|
+
const m = String(r.label ?? "").match(/^OIML-([A-Z]+)-(\d+)(?:-([A-Za-z0-9]+))?-(\d{4})$/);
|
|
2026
|
+
return m ? `OIML ${m[1]} ${m[2]}${m[3] ? `-${m[3]}` : ""} (${m[4]})` : String(r.label ?? "");
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
boundaryNote = boundaryNoteText(match, citing);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
1957
2032
|
try {
|
|
1958
2033
|
const tR = Date.now();
|
|
1959
2034
|
if (declaredCtx?.kind === "account") {
|
|
@@ -2047,7 +2122,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2047
2122
|
q.lang,
|
|
2048
2123
|
keptHistory,
|
|
2049
2124
|
// stage-extracted graph facts (GraphRAG) ride the same note channel
|
|
2050
|
-
[processNote, eNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, licenseNote, ...retrieved.notes ?? []].filter(Boolean).join("\n") || void 0,
|
|
2125
|
+
[processNote, eNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, boundaryNote, licenseNote, ...retrieved.notes ?? []].filter(Boolean).join("\n") || void 0,
|
|
2051
2126
|
summary,
|
|
2052
2127
|
budget
|
|
2053
2128
|
);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
handleMemories,
|
|
8
8
|
scoreJudge,
|
|
9
9
|
standardForDocNumber
|
|
10
|
-
} from "../../chunk-
|
|
10
|
+
} from "../../chunk-AN4VFBBZ.js";
|
|
11
11
|
import {
|
|
12
12
|
buildMessages,
|
|
13
13
|
citations,
|
|
@@ -979,7 +979,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
979
979
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
980
980
|
body: JSON.stringify({ ...args, stream: false })
|
|
981
981
|
});
|
|
982
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
982
|
+
const res = name === "ask" ? await (await import("../../ask-HCZ4CDUR.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-RFKT7ZZY.js")).handleSearch(env, ctx, inner, tier, key);
|
|
983
983
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
984
984
|
});
|
|
985
985
|
if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
|
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@oimlsmart/oiml-pubid": "^1.2.1"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {},
|
|
37
|
-
"version": "0.2.
|
|
37
|
+
"version": "0.2.19",
|
|
38
38
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
39
39
|
"license": "BSD-3-Clause",
|
|
40
40
|
"type": "module",
|
|
@@ -173,7 +173,8 @@ function pickTable(nodes: { node_id: string; content: any }[], query: string): {
|
|
|
173
173
|
for (const t of tokens(n.node_id.replace("/table/", ""))) {
|
|
174
174
|
if (t.length >= 3 && queryLower.includes(t)) score += 2;
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
// the D1 projection keeps the title under `definition`
|
|
177
|
+
for (const w of String(c.name ?? c.definition ?? "").toLowerCase().split(/[^a-z0-9.]+/)) {
|
|
177
178
|
if (w.length >= 4 && queryLower.includes(w)) score += 1;
|
|
178
179
|
}
|
|
179
180
|
// the question's stated unit existing as this table's interval unit,
|
|
@@ -206,13 +207,18 @@ export function evaluateAggregation(
|
|
|
206
207
|
: "lookup";
|
|
207
208
|
if (!nodes.length) return null;
|
|
208
209
|
const node = pickTable(nodes, query);
|
|
210
|
+
|
|
209
211
|
if (!node) return null;
|
|
210
212
|
const content = (node.content && typeof node.content === "object" ? node.content : {}) as Record<string, any>;
|
|
211
213
|
const payload = (content.payload ?? {}) as { columns?: Column[]; rows?: unknown[] };
|
|
212
214
|
const cols = Array.isArray(payload.columns) ? payload.columns : [];
|
|
213
215
|
const rows = (Array.isArray(payload.rows) ? payload.rows : []).filter((r) => Array.isArray(r)) as string[][];
|
|
214
216
|
if (!cols.length || !rows.length) return null;
|
|
215
|
-
const
|
|
217
|
+
const rawTitle = String(content.name ?? content.definition ?? node.node_id.replace("/table/", ""));
|
|
218
|
+
// display titles are cut before the normative parenthetical and at a
|
|
219
|
+
// word boundary — the full definition stays in the payload
|
|
220
|
+
const cut = rawTitle.indexOf(" (");
|
|
221
|
+
const tableTitle = cut > 0 ? rawTitle.slice(0, cut) : rawTitle.slice(0, 120);
|
|
216
222
|
|
|
217
223
|
const cite = (what: string) =>
|
|
218
224
|
`COMPUTED (${operation}) — ${what}, read from the typed table "${tableTitle}" (${node.node_id}). Present this result and cite the table's clause; the value is machine-computed from the table payload, do not recompute or round it differently.`;
|
|
@@ -23,6 +23,7 @@ import { bindModelNode, licenseBoundaryNote, licenseBoundaryRefusal, licensedEnt
|
|
|
23
23
|
import { evaluate as machineEvaluate, verdictNote } from "./verdict";
|
|
24
24
|
import { evaluateConditionSets, quantitiesIn, type ConditionVerdict } from "./conditions";
|
|
25
25
|
import { evaluateAggregation, type AggregationVerdict } from "./aggregation";
|
|
26
|
+
import { matchLicensedTopic, boundaryNoteText } from "./boundary";
|
|
26
27
|
import { detectDraftIntent, prepareDraft } from "./drafts";
|
|
27
28
|
import { memoryNote } from "./memories";
|
|
28
29
|
import { entitlementScope, resolveRequestScope, requestSalt } from "./requestScope";
|
|
@@ -725,6 +726,22 @@ async function handleAsk(
|
|
|
725
726
|
// quantities alongside severity vocabulary; the candidate sets come
|
|
726
727
|
// from the model node store (kind condition_set), license-gated like
|
|
727
728
|
// every model lane.
|
|
729
|
+
// the model lanes' node store read: the doc hint joins the STANDARD id
|
|
730
|
+
// (iec-60068-2-78 carries the part; oiml-r60 does not) — when the hint
|
|
731
|
+
// names a part of a part-less package ("R 60-1"), retry on the stem
|
|
732
|
+
const modelNodeRows = async (kind: string, docNum: string | undefined) => {
|
|
733
|
+
const attempt = (num: string | undefined) => {
|
|
734
|
+
const sql = num
|
|
735
|
+
? `SELECT standard, node_id, content FROM model_nodes WHERE kind = '${kind}' AND standard LIKE '%' || ?1`
|
|
736
|
+
: `SELECT standard, node_id, content FROM model_nodes WHERE kind = '${kind}'`;
|
|
737
|
+
return num ? env.DB.prepare(sql).bind(num) : env.DB.prepare(sql);
|
|
738
|
+
};
|
|
739
|
+
let rows = await attempt(docNum).all().catch(() => ({ results: [] }));
|
|
740
|
+
if (!(rows.results ?? []).length && docNum && docNum.includes("-")) {
|
|
741
|
+
rows = await attempt(docNum.replace(/-\d+$/, "")).all().catch(() => ({ results: [] }));
|
|
742
|
+
}
|
|
743
|
+
return rows;
|
|
744
|
+
};
|
|
728
745
|
let conditionVerdict: ConditionVerdict | null = null;
|
|
729
746
|
let conditionStandard: string | null = null;
|
|
730
747
|
if (!machineVerdict && !boundModel && P().publisher.features?.model_plane) {
|
|
@@ -735,11 +752,7 @@ async function handleAsk(
|
|
|
735
752
|
const docNum = modelDocHint?.doc_number;
|
|
736
753
|
// the doc number joins the STANDARD id (iec-60068-2-78), which is
|
|
737
754
|
// the doc number prefixed with the package-family prefix
|
|
738
|
-
const
|
|
739
|
-
? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' AND standard LIKE '%' || ?1"
|
|
740
|
-
: "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set'";
|
|
741
|
-
const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
|
|
742
|
-
const rows = await stmt.all().catch(() => ({ results: [] }));
|
|
755
|
+
const rows = await modelNodeRows("condition_set", docNum);
|
|
743
756
|
const candidates = (rows.results ?? []).filter((r: any) => {
|
|
744
757
|
const entry = licensedEntryForPackage(String(r.standard));
|
|
745
758
|
return !entry || (standardKeys?.has(entry.key) ?? false);
|
|
@@ -801,11 +814,7 @@ async function handleAsk(
|
|
|
801
814
|
let aggregationStandard: string | null = null;
|
|
802
815
|
if (!machineVerdict && !boundModel && !conditionVerdict && P().publisher.features?.model_plane) {
|
|
803
816
|
const docNum = modelDocHint?.doc_number;
|
|
804
|
-
const
|
|
805
|
-
? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'table' AND standard LIKE '%' || ?1"
|
|
806
|
-
: "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'table'";
|
|
807
|
-
const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
|
|
808
|
-
const rows = await stmt.all().catch(() => ({ results: [] }));
|
|
817
|
+
const rows = await modelNodeRows("table", docNum);
|
|
809
818
|
const candidates = (rows.results ?? []).filter((r: any) => {
|
|
810
819
|
const entry = licensedEntryForPackage(String(r.standard));
|
|
811
820
|
return !entry || (standardKeys?.has(entry.key) ?? false);
|
|
@@ -838,6 +847,29 @@ async function handleAsk(
|
|
|
838
847
|
}
|
|
839
848
|
: null;
|
|
840
849
|
if (aggregationVerdict) console.log("aggregation engine:", aggregationVerdict.operation, aggregationVerdict.table, "→", aggregationVerdict.value);
|
|
850
|
+
// ── the licensed boundary note (TODO.rag/12): an unentitled question
|
|
851
|
+
// that is topically ABOUT a licensed standard gets the boundary
|
|
852
|
+
// posture — the citation graph names the public publications that
|
|
853
|
+
// reference the licensed document, and the note instructs the model
|
|
854
|
+
// to attribute, never to recite the licensed parameters.
|
|
855
|
+
let boundaryNote: string | null = null;
|
|
856
|
+
if (P().sources?.licensed?.length) {
|
|
857
|
+
const match = matchLicensedTopic(q.query, P().sources.licensed);
|
|
858
|
+
if (match && !(standardKeys?.has(match.entry.key) ?? false)) {
|
|
859
|
+
const docNum = match.entry.doc_number ?? "";
|
|
860
|
+
let citing: string[] = [];
|
|
861
|
+
if (docNum) {
|
|
862
|
+
const rows = await env.DB.prepare(
|
|
863
|
+
"SELECT n.label AS label FROM graph_edges e JOIN graph_nodes n ON e.src = n.id WHERE e.kind = 'cites' AND e.dst LIKE ?1 LIMIT 4",
|
|
864
|
+
).bind(`%${docNum}%`).all().catch(() => ({ results: [] }));
|
|
865
|
+
citing = (rows.results ?? []).map((r: any) => {
|
|
866
|
+
const m = String(r.label ?? "").match(/^OIML-([A-Z]+)-(\d+)(?:-([A-Za-z0-9]+))?-(\d{4})$/);
|
|
867
|
+
return m ? `OIML ${m[1]} ${m[2]}${m[3] ? `-${m[3]}` : ""} (${m[4]})` : String(r.label ?? "");
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
boundaryNote = boundaryNoteText(match, citing);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
841
873
|
try {
|
|
842
874
|
const tR = Date.now();
|
|
843
875
|
// ── The "my account" live read (TODO.ai-platform/03) — resolved
|
|
@@ -987,7 +1019,7 @@ async function handleAsk(
|
|
|
987
1019
|
q.lang,
|
|
988
1020
|
keptHistory,
|
|
989
1021
|
// stage-extracted graph facts (GraphRAG) ride the same note channel
|
|
990
|
-
[processNote, eNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, licenseNote, ...(retrieved.notes ?? [])].filter(Boolean).join("\n") || undefined,
|
|
1022
|
+
[processNote, eNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, boundaryNote, licenseNote, ...(retrieved.notes ?? [])].filter(Boolean).join("\n") || undefined,
|
|
991
1023
|
summary,
|
|
992
1024
|
budget,
|
|
993
1025
|
);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// The licensed boundary note (TODO.rag/12): when a question is about a
|
|
2
|
+
// topic that matches a LICENSED standard the caller is not entitled to,
|
|
3
|
+
// the note gives the answer model the boundary posture — name the
|
|
4
|
+
// licensed document as the authoritative procedure, name the public
|
|
5
|
+
// publications that reference it, and never recite the licensed
|
|
6
|
+
// parameters as if from the source. Pure matching/text lives here; the
|
|
7
|
+
// inverse-cites D1 read lives at the call site.
|
|
8
|
+
|
|
9
|
+
export interface LicensedEntry {
|
|
10
|
+
key: string;
|
|
11
|
+
package?: string;
|
|
12
|
+
doc_number?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LicensedMatch {
|
|
17
|
+
entry: LicensedEntry;
|
|
18
|
+
matched: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// title words that carry no topical identity — a question about "tests"
|
|
22
|
+
// or "immunity" must not alone light the boundary
|
|
23
|
+
const TITLE_STOPWORDS = new Set([
|
|
24
|
+
"environmental", "testing", "test", "tests", "guidance", "generic",
|
|
25
|
+
"standards", "standard", "electromagnetic", "compatibility",
|
|
26
|
+
"environment", "description", "measurement", "techniques", "immunity",
|
|
27
|
+
"residential", "commercial", "industrial", "environments", "and", "for", "the",
|
|
28
|
+
"iec", "iso",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export function distinctiveTokens(title: string): string[] {
|
|
32
|
+
return title
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.split(/[^a-z0-9.]+/)
|
|
35
|
+
.filter((w) => w.length >= 3 && !TITLE_STOPWORDS.has(w) && !/^[0-9:-]+$/.test(w));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The licensed entry whose distinctive title tokens best match the
|
|
39
|
+
* question. Needs TWO token hits (or one hyphenated-compound hit) —
|
|
40
|
+
* a single shared word is not a topic match. */
|
|
41
|
+
export function matchLicensedTopic(query: string, licensed: LicensedEntry[]): LicensedMatch | null {
|
|
42
|
+
const words = new Set(query.toLowerCase().split(/[^a-z0-9.]+/).filter(Boolean));
|
|
43
|
+
let best: { entry: LicensedEntry; matched: string[] } | null = null;
|
|
44
|
+
for (const entry of licensed) {
|
|
45
|
+
if (!entry.title) continue;
|
|
46
|
+
const matched = distinctiveTokens(entry.title).filter((w) => words.has(w));
|
|
47
|
+
if (matched.length < 2) continue;
|
|
48
|
+
if (!best || matched.length > best.matched.length) best = { entry, matched };
|
|
49
|
+
}
|
|
50
|
+
return best;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The note text: the posture instruction for an unentitled match. */
|
|
54
|
+
export function boundaryNoteText(match: LicensedMatch, citing: string[]): string {
|
|
55
|
+
const doc = match.entry.doc_number ?? match.entry.key;
|
|
56
|
+
const title = match.entry.title ?? doc;
|
|
57
|
+
const refs = citing.length
|
|
58
|
+
? `The public corpus references it from ${citing.join(", ")}.`
|
|
59
|
+
: "";
|
|
60
|
+
return (
|
|
61
|
+
`LICENSED BOUNDARY — ${title} (IEC ${doc}) is licensed content in this ` +
|
|
62
|
+
`deployment; its procedure is NOT part of the public corpus you are grounded in. ` +
|
|
63
|
+
`${refs} When answering: name the licensed document as the authoritative source of ` +
|
|
64
|
+
`the procedure and say it is available to entitled callers; do NOT recite its ` +
|
|
65
|
+
`conditioning or severity parameters (specific temperatures, humidity levels, ` +
|
|
66
|
+
`durations or cycle counts) as if from the source — describe only what the public ` +
|
|
67
|
+
`grounding passages themselves state, attributed to their own publications.`
|
|
68
|
+
);
|
|
69
|
+
}
|