@konneal/engine 0.2.74 → 0.2.76

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,12 +1,12 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-CNSTPYX3.js";
3
+ } from "./chunk-64G44J7U.js";
4
4
  import "./chunk-7HWJOFTQ.js";
5
5
  import "./chunk-SPKPH54J.js";
6
6
  import "./chunk-A3QHHUN5.js";
7
7
  import "./chunk-LVZTWUVJ.js";
8
8
  import "./chunk-2PCUAJJO.js";
9
- import "./chunk-3IRX4B7Q.js";
9
+ import "./chunk-VGVTIPFW.js";
10
10
  import "./chunk-3FYJM7LH.js";
11
11
  export {
12
12
  handleAsk
@@ -6,7 +6,7 @@ import {
6
6
  registerNote,
7
7
  registerTokens,
8
8
  searchRegister
9
- } from "./chunk-3IRX4B7Q.js";
9
+ } from "./chunk-VGVTIPFW.js";
10
10
  import "./chunk-3FYJM7LH.js";
11
11
  export {
12
12
  buildRegisterQuery,
@@ -72,7 +72,7 @@ import {
72
72
  certificateLinks,
73
73
  registerNote,
74
74
  searchRegister
75
- } from "./chunk-3IRX4B7Q.js";
75
+ } from "./chunk-VGVTIPFW.js";
76
76
  import {
77
77
  P
78
78
  } from "./chunk-3FYJM7LH.js";
@@ -1896,7 +1896,7 @@ async function runTool(db, call) {
1896
1896
  if (call.name !== "register_search") return null;
1897
1897
  const query = String(call.args?.query ?? "").trim().slice(0, 160);
1898
1898
  if (!query) return null;
1899
- const { searchRegister: searchRegister2, registerNote: registerNote2 } = await import("./certificates-EMHFAUWC.js");
1899
+ const { searchRegister: searchRegister2, registerNote: registerNote2 } = await import("./certificates-DNRUOYRF.js");
1900
1900
  const reg = await searchRegister2(db, query, true);
1901
1901
  const output = reg?.rows?.length ? registerNote2(reg.rows) : `No certificate was found for "${query}" in the certificates database (the register snapshot). State this as the search's result, with the searched string visible.`;
1902
1902
  return { name: call.name, query, output };
@@ -1992,18 +1992,18 @@ function catalogNote(anchors) {
1992
1992
  return `The COMPLETE name index of the platform's operations:
1993
1993
  ${anchors.map((a) => `- ${a}`).join("\n")}`;
1994
1994
  }
1995
- async function operationsCatalogNote(db, query) {
1996
- if (!isOperationIntent(query)) return void 0;
1995
+ async function operationsLane(db, query) {
1996
+ if (!isOperationIntent(query)) return { matched: [] };
1997
1997
  for (let attempt = 0; attempt < 2; attempt++) {
1998
- const note = await operationsCatalogNoteOnce(db, query, attempt > 0);
1999
- if (note !== void 0) return note;
1998
+ const lane = await operationsLaneOnce(db, query, attempt > 0);
1999
+ if (lane !== void 0) return lane;
2000
2000
  }
2001
- return void 0;
2001
+ return { matched: [] };
2002
2002
  }
2003
- async function operationsCatalogNoteOnce(db, query, retried) {
2003
+ async function operationsLaneOnce(db, query, retried) {
2004
2004
  try {
2005
- const rows = (await db.prepare("SELECT clause_anchor AS anchor, substr(text, 1, 300) AS text FROM chunks WHERE corpus = 'smart-ops' AND clause_anchor IS NOT NULL ORDER BY clause_anchor").all()).results ?? [];
2006
- const opRows = rows.map((r) => ({ anchor: String(r.anchor ?? ""), text: String(r.text ?? "") })).filter((r) => r.anchor);
2005
+ const rows = (await db.prepare("SELECT clause_anchor AS anchor, substr(text, 1, 300) AS text, docidentifier FROM chunks WHERE corpus = 'smart-ops' AND clause_anchor IS NOT NULL ORDER BY clause_anchor").all()).results ?? [];
2006
+ const opRows = rows.map((r) => ({ anchor: String(r.anchor ?? ""), text: String(r.text ?? ""), docidentifier: String(r.docidentifier ?? "Platform API") })).filter((r) => r.anchor);
2007
2007
  const matched = matchOperations(query, opRows);
2008
2008
  const parts = [];
2009
2009
  if (matched.length) {
@@ -2011,7 +2011,8 @@ async function operationsCatalogNoteOnce(db, query, retried) {
2011
2011
  ${matched.map((m) => `- ${m.text}`).join("\n")}`);
2012
2012
  }
2013
2013
  parts.push(catalogNote(opRows.map((r) => r.anchor)));
2014
- return parts.filter(Boolean).join("\n\n") || void 0;
2014
+ const note = parts.filter(Boolean).join("\n\n") || void 0;
2015
+ return { note, matched };
2015
2016
  } catch (e) {
2016
2017
  console.error(`operations-catalog: D1 read failed${retried ? " twice" : ""} \u2014 the note is dropped`, String(e).slice(0, 120));
2017
2018
  return void 0;
@@ -2049,23 +2050,31 @@ function nameplateRegisterQuery(np, question) {
2049
2050
 
2050
2051
  // workers/worker_public/src/nameplate.ts
2051
2052
  async function extractNameplate(ai, model, image) {
2052
- try {
2053
- const res = await ai.run(model, {
2054
- messages: [
2055
- { role: "system", content: nameplate_default.trimEnd() },
2056
- { role: "user", content: [{ type: "image_url", image_url: { url: image } }] }
2057
- ],
2058
- max_tokens: 512,
2059
- reasoning_effort: "low"
2060
- });
2061
- const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
2062
- const np = parseNameplate(text ?? "");
2063
- console.log(`nameplate-extract: ${np ? `${np.manufacturer} / ${np.model}` : `no parse (${(text ?? "").slice(0, 120)})`}`);
2064
- return np;
2065
- } catch (e) {
2066
- console.log(`nameplate-extract: FAILED ${String(e).slice(0, 160)}`);
2067
- return null;
2053
+ for (let attempt = 0; attempt < 2; attempt++) {
2054
+ try {
2055
+ const res = await ai.run(model, {
2056
+ messages: [
2057
+ { role: "system", content: nameplate_default.trimEnd() },
2058
+ { role: "user", content: [{ type: "image_url", image_url: { url: image } }] }
2059
+ ],
2060
+ // the budget rises with the retry's effort — elevated effort under
2061
+ // a flat budget starves the answer on this model (measured,
2062
+ // 2026-09-18: 5/38 at medium with the flat budget)
2063
+ max_tokens: attempt === 0 ? 512 : 1536,
2064
+ reasoning_effort: attempt === 0 ? "low" : "medium"
2065
+ });
2066
+ const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
2067
+ const np = parseNameplate(text ?? "");
2068
+ if (np) {
2069
+ console.log(`nameplate-extract: ${np.manufacturer} / ${np.model}${attempt ? " (on retry)" : ""}`);
2070
+ return np;
2071
+ }
2072
+ console.log(`nameplate-extract: no parse on attempt ${attempt + 1} (${(text ?? "").slice(0, 120)})`);
2073
+ } catch (e) {
2074
+ console.log(`nameplate-extract: FAILED attempt ${attempt + 1} ${String(e).slice(0, 160)}`);
2075
+ }
2068
2076
  }
2077
+ return null;
2069
2078
  }
2070
2079
 
2071
2080
  // workers/worker_public/src/ask.ts
@@ -2461,7 +2470,7 @@ async function handleAsk(env, ctx, req, tier, key) {
2461
2470
  }
2462
2471
  console.log("register-search:", JSON.stringify({ shaped: !!register, tokens: register?.tokens ?? null, rows: register?.rows?.length ?? null, first: register?.rows?.[0]?.num ?? null }));
2463
2472
  const regNote = register ? [registerNote(register.rows), certificateLinks(register.rows)].filter(Boolean).join("\n") : void 0;
2464
- const opsCatalog = await operationsCatalogNote(env.DB, q.query);
2473
+ const ops = await operationsLane(env.DB, q.query);
2465
2474
  if (understanding?.intent === "conversational") {
2466
2475
  const queryHash2 = await sha256Hex(q.query);
2467
2476
  const messages2 = [
@@ -2807,13 +2816,31 @@ Answer account questions from these records ONLY: name the record when you use i
2807
2816
  return matched.length ? matched : g;
2808
2817
  })();
2809
2818
  const vocabNote = glossaryForNote.length ? "Vocabulary binding \u2014 defined terms in the indexed corpus that may name this question's subject:\n" + glossaryForNote.map((g) => `- ${g.term} (${g.docidentifier}): ${g.definition}`).join("\n") + "\nIf the question describes a symptom or behavior in everyday words, OPEN the answer by naming the matching defined term, quote its definition, and cite its defining publication; keep using that term throughout. Match TIME SCALE carefully: change under a constant load over minutes/hours is creep; change over months/years of use is span stability or durability \u2014 do not call long-term drift creep." : void 0;
2819
+ const opsHits = ops.matched.map((m) => ({
2820
+ id: `ops:${m.anchor}`,
2821
+ score: 0.999,
2822
+ metadata: {
2823
+ doc_id: "smart-ops:catalog",
2824
+ docidentifier: m.docidentifier ?? "Platform API",
2825
+ doctype: "api-operation",
2826
+ doc_number: "",
2827
+ edition: "",
2828
+ language: "en",
2829
+ clause_anchor: m.anchor,
2830
+ clause_title: `Platform operation ${m.anchor}`,
2831
+ tier: "curated",
2832
+ corpus: "smart-ops",
2833
+ text_ref: ""
2834
+ },
2835
+ text: `${m.anchor} \u2014 ${m.text}`
2836
+ }));
2810
2837
  const { messages, usedHits } = buildMessages(
2811
2838
  q.query,
2812
- hits,
2839
+ [...opsHits, ...hits],
2813
2840
  q.lang,
2814
2841
  keptHistory,
2815
2842
  // stage-extracted graph facts (GraphRAG) ride the same note channel
2816
- [opsCatalog, processNote, eNote, regNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, boundaryNote, licenseNote, ...retrieved.notes ?? []].filter(Boolean).join("\n") || void 0,
2843
+ [ops.note, processNote, eNote, regNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, boundaryNote, licenseNote, ...retrieved.notes ?? []].filter(Boolean).join("\n") || void 0,
2817
2844
  summary,
2818
2845
  budget
2819
2846
  );
@@ -121,7 +121,7 @@ function registerNote(rows) {
121
121
  ].join("\n");
122
122
  }
123
123
  function certificateLinks(rows) {
124
- const links = rows.filter((r) => r.pdf_key).map((r) => `- ${r.num}: https://www.ommisa.org/cert-pdf/${r.pdf_key}`);
124
+ const links = rows.filter((r) => r.pdf_key).map((r) => `- ${r.num}: https://www.oimlsmart.org/cert-pdf/${r.pdf_key}`);
125
125
  return links.length ? `The certificates' documents:
126
126
  ${links.join("\n")}` : "";
127
127
  }
@@ -6,9 +6,26 @@ export declare function anchorWords(anchor: string): string[];
6
6
  export declare function matchOperations(query: string, rows: {
7
7
  anchor: string;
8
8
  text: string;
9
+ docidentifier?: string;
9
10
  }[]): {
10
11
  anchor: string;
11
12
  text: string;
13
+ docidentifier?: string;
12
14
  }[];
13
15
  export declare function catalogNote(anchors: string[]): string;
14
- export declare function operationsCatalogNote(db: any, query: string): Promise<string | undefined>;
16
+ /** The lane's whole result: the authoritative note (matched lines +
17
+ * name index) AND the matched rows themselves — the ask path rides the
18
+ * matched operations as CITABLE PASSAGES, because the refusal logic
19
+ * keys on passages and a side-note cannot outrank the passages'
20
+ * silence (observed live 2026-09-29: the note was present and the
21
+ * model still refused "the only API operation in the current context
22
+ * is the blob download"). */
23
+ export interface OperationsLane {
24
+ note?: string;
25
+ matched: {
26
+ anchor: string;
27
+ text: string;
28
+ docidentifier?: string;
29
+ }[];
30
+ }
31
+ export declare function operationsLane(db: any, query: string): Promise<OperationsLane>;
@@ -6,7 +6,7 @@ import {
6
6
  modelGroundingBlock,
7
7
  scoreFaithfulness,
8
8
  scoreJudge
9
- } from "../../chunk-CNSTPYX3.js";
9
+ } from "../../chunk-64G44J7U.js";
10
10
  import {
11
11
  handleSearch
12
12
  } from "../../chunk-KO6DRI7C.js";
@@ -63,7 +63,7 @@ import {
63
63
  sha256Hex,
64
64
  today
65
65
  } from "../../chunk-2PCUAJJO.js";
66
- import "../../chunk-3IRX4B7Q.js";
66
+ import "../../chunk-VGVTIPFW.js";
67
67
  import {
68
68
  P,
69
69
  setProfile
@@ -959,7 +959,7 @@ async function handleMcp(env, ctx, req, tier, key) {
959
959
  // stream:false forces the JSON lane (anon defaults to SSE)
960
960
  body: JSON.stringify({ ...args, stream: false })
961
961
  });
962
- const res = name === "ask" ? await (await import("../../ask-FHA56VIN.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LVUQLA3C.js")).handleSearch(env, ctx, inner, tier, key);
962
+ const res = name === "ask" ? await (await import("../../ask-PK75QJ66.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LVUQLA3C.js")).handleSearch(env, ctx, inner, tier, key);
963
963
  return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
964
964
  });
965
965
  if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
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.74",
30
+ "version": "0.2.76",
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",
@@ -41,7 +41,7 @@ import { chargeTokens, checkQuota, clientIp, estimateTokens, telemetry, tokenBud
41
41
  import { graphExpand, editionNote } from "./graph";
42
42
  import { registerNote, searchRegister, certificateLinks } from "./certificates";
43
43
  import { TOOL_DECLARATION, parseToolCall, runTool, toolNote } from "./tools";
44
- import { operationsCatalogNote } from "./operations.ts";
44
+ import { operationsLane } from "./operations.ts";
45
45
  import { extractNameplate, nameplateRegisterQuery } from "./nameplate.ts";
46
46
  import { P } from "./profile.ts";
47
47
 
@@ -677,7 +677,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
677
677
  }
678
678
  console.log("register-search:", JSON.stringify({ shaped: !!register, tokens: register?.tokens ?? null, rows: register?.rows?.length ?? null, first: register?.rows?.[0]?.num ?? null }));
679
679
  const regNote = register ? [registerNote(register.rows), certificateLinks(register.rows)].filter(Boolean).join("\n") : undefined;
680
- const opsCatalog = await operationsCatalogNote(env.DB, q.query);
680
+ const ops = await operationsLane(env.DB, q.query);
681
681
 
682
682
  // Conversational route, decided by query UNDERSTANDING (any language, any
683
683
  // phrasing) — not string matching. No retrieval: nothing in the corpus
@@ -1182,13 +1182,34 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1182
1182
  glossaryForNote.map((g) => `- ${g.term} (${g.docidentifier}): ${g.definition}`).join("\n") +
1183
1183
  "\nIf the question describes a symptom or behavior in everyday words, OPEN the answer by naming the matching defined term, quote its definition, and cite its defining publication; keep using that term throughout. Match TIME SCALE carefully: change under a constant load over minutes/hours is creep; change over months/years of use is span stability or durability — do not call long-term drift creep."
1184
1184
  : undefined;
1185
+ // the matched operations ride as CITABLE PASSAGES ahead of the
1186
+ // retrieved hits — the refusal logic and the citation machinery key on
1187
+ // passages, and a side-note cannot outrank the passages' silence
1188
+ const opsHits: Hit[] = ops.matched.map((m) => ({
1189
+ id: `ops:${m.anchor}`,
1190
+ score: 0.999,
1191
+ metadata: {
1192
+ doc_id: "smart-ops:catalog",
1193
+ docidentifier: m.docidentifier ?? "Platform API",
1194
+ doctype: "api-operation",
1195
+ doc_number: "",
1196
+ edition: "",
1197
+ language: "en",
1198
+ clause_anchor: m.anchor,
1199
+ clause_title: `Platform operation ${m.anchor}`,
1200
+ tier: "curated",
1201
+ corpus: "smart-ops",
1202
+ text_ref: "",
1203
+ },
1204
+ text: `${m.anchor} — ${m.text}`,
1205
+ }));
1185
1206
  const { messages, usedHits } = buildMessages(
1186
1207
  q.query,
1187
- hits,
1208
+ [...opsHits, ...hits],
1188
1209
  q.lang,
1189
1210
  keptHistory,
1190
1211
  // stage-extracted graph facts (GraphRAG) ride the same note channel
1191
- [opsCatalog, processNote, eNote, regNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, boundaryNote, licenseNote, ...(retrieved.notes ?? [])].filter(Boolean).join("\n") || undefined,
1212
+ [ops.note, processNote, eNote, regNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, boundaryNote, licenseNote, ...(retrieved.notes ?? [])].filter(Boolean).join("\n") || undefined,
1192
1213
  summary,
1193
1214
  budget,
1194
1215
  );
@@ -134,7 +134,7 @@ export function registerNote(rows: RegisterRow[]): string {
134
134
  * is in the R2 plane (TODO 7) — the user gets the document, not just
135
135
  * the fact. */
136
136
  export function certificateLinks(rows: RegisterRow[]): string {
137
- const links = rows.filter((r) => r.pdf_key).map((r) => `- ${r.num}: https://www.ommisa.org/cert-pdf/${r.pdf_key}`);
137
+ const links = rows.filter((r) => r.pdf_key).map((r) => `- ${r.num}: https://www.oimlsmart.org/cert-pdf/${r.pdf_key}`);
138
138
  return links.length ? `The certificates' documents:\n${links.join("\n")}` : "";
139
139
  }
140
140
 
@@ -14,23 +14,36 @@ export type { Nameplate };
14
14
  export { nameplateRegisterQuery };
15
15
 
16
16
  export async function extractNameplate(ai: any, model: string, image: string): Promise<Nameplate | null> {
17
- try {
18
- const res: any = await ai.run(model, {
19
- messages: [
20
- { role: "system", content: nameplatePrompt.trimEnd() },
21
- { role: "user", content: [{ type: "image_url", image_url: { url: image } }] },
22
- ],
23
- max_tokens: 512,
24
- reasoning_effort: "low",
25
- });
26
- const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
27
- const np = parseNameplate(text ?? "");
28
- console.log(`nameplate-extract: ${np ? `${np.manufacturer} / ${np.model}` : `no parse (${(text ?? "").slice(0, 120)})`}`);
29
- return np;
30
- } catch (e) {
31
- console.log(`nameplate-extract: FAILED ${String(e).slice(0, 160)}`);
32
- return null; // the bridge is optional — the ask never depends on it
17
+ // one retry, decided on the RESULT not the error: the observed live
18
+ // failure (2026-10-03) is a structurally valid response carrying null
19
+ // fields — the same image the answer model read perfectly a moment
20
+ // later. A transient vision-call miss must not strand a certificate
21
+ // question with no register rows.
22
+ for (let attempt = 0; attempt < 2; attempt++) {
23
+ try {
24
+ const res: any = await ai.run(model, {
25
+ messages: [
26
+ { role: "system", content: nameplatePrompt.trimEnd() },
27
+ { role: "user", content: [{ type: "image_url", image_url: { url: image } }] },
28
+ ],
29
+ // the budget rises with the retry's effort — elevated effort under
30
+ // a flat budget starves the answer on this model (measured,
31
+ // 2026-09-18: 5/38 at medium with the flat budget)
32
+ max_tokens: attempt === 0 ? 512 : 1536,
33
+ reasoning_effort: attempt === 0 ? "low" : "medium",
34
+ });
35
+ const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
36
+ const np = parseNameplate(text ?? "");
37
+ if (np) {
38
+ console.log(`nameplate-extract: ${np.manufacturer} / ${np.model}${attempt ? " (on retry)" : ""}`);
39
+ return np;
40
+ }
41
+ console.log(`nameplate-extract: no parse on attempt ${attempt + 1} (${(text ?? "").slice(0, 120)})`);
42
+ } catch (e) {
43
+ console.log(`nameplate-extract: FAILED attempt ${attempt + 1} ${String(e).slice(0, 160)}`);
44
+ }
33
45
  }
46
+ return null; // the bridge is optional — the ask never depends on it
34
47
  }
35
48
 
36
49
  export const nameplateModel = () => MODELS.member;
@@ -38,7 +38,7 @@ export function anchorWords(anchor: string): string[] {
38
38
 
39
39
  const singular = (w: string) => (w.length > 3 && w.endsWith("s") ? w.slice(0, -1) : w);
40
40
 
41
- export function matchOperations(query: string, rows: { anchor: string; text: string }[]): { anchor: string; text: string }[] {
41
+ export function matchOperations(query: string, rows: { anchor: string; text: string; docidentifier?: string }[]): { anchor: string; text: string; docidentifier?: string }[] {
42
42
  const q = query.toLowerCase();
43
43
  const stems = ACTION_STEMS.filter((s) => q.includes(s));
44
44
  const objects = query
@@ -68,29 +68,42 @@ export function catalogNote(anchors: string[]): string {
68
68
  return `The COMPLETE name index of the platform's operations:\n${anchors.map((a) => `- ${a}`).join("\n")}`;
69
69
  }
70
70
 
71
- export async function operationsCatalogNote(db: any, query: string): Promise<string | undefined> {
72
- if (!isOperationIntent(query)) return undefined;
71
+ /** The lane's whole result: the authoritative note (matched lines +
72
+ * name index) AND the matched rows themselves — the ask path rides the
73
+ * matched operations as CITABLE PASSAGES, because the refusal logic
74
+ * keys on passages and a side-note cannot outrank the passages'
75
+ * silence (observed live 2026-09-29: the note was present and the
76
+ * model still refused "the only API operation in the current context
77
+ * is the blob download"). */
78
+ export interface OperationsLane {
79
+ note?: string;
80
+ matched: { anchor: string; text: string; docidentifier?: string }[];
81
+ }
82
+
83
+ export async function operationsLane(db: any, query: string): Promise<OperationsLane> {
84
+ if (!isOperationIntent(query)) return { matched: [] };
73
85
  // one retry, and the failure is LOUD: a silently dropped note answers
74
86
  // an operation question with the OIML refusal (observed live 2026-09-29
75
87
  // — a transient D1 read failed into the catch and the lane vanished)
76
88
  for (let attempt = 0; attempt < 2; attempt++) {
77
- const note = await operationsCatalogNoteOnce(db, query, attempt > 0);
78
- if (note !== undefined) return note;
89
+ const lane = await operationsLaneOnce(db, query, attempt > 0);
90
+ if (lane !== undefined) return lane;
79
91
  }
80
- return undefined;
92
+ return { matched: [] };
81
93
  }
82
94
 
83
- async function operationsCatalogNoteOnce(db: any, query: string, retried: boolean): Promise<string | undefined> {
95
+ async function operationsLaneOnce(db: any, query: string, retried: boolean): Promise<OperationsLane | undefined> {
84
96
  try {
85
- const rows = (await db.prepare("SELECT clause_anchor AS anchor, substr(text, 1, 300) AS text FROM chunks WHERE corpus = 'smart-ops' AND clause_anchor IS NOT NULL ORDER BY clause_anchor").all()).results ?? [];
86
- const opRows = (rows as any[]).map((r) => ({ anchor: String(r.anchor ?? ""), text: String(r.text ?? "") })).filter((r) => r.anchor);
97
+ const rows = (await db.prepare("SELECT clause_anchor AS anchor, substr(text, 1, 300) AS text, docidentifier FROM chunks WHERE corpus = 'smart-ops' AND clause_anchor IS NOT NULL ORDER BY clause_anchor").all()).results ?? [];
98
+ const opRows = (rows as any[]).map((r) => ({ anchor: String(r.anchor ?? ""), text: String(r.text ?? ""), docidentifier: String(r.docidentifier ?? "Platform API") })).filter((r) => r.anchor);
87
99
  const matched = matchOperations(query, opRows);
88
100
  const parts: string[] = [];
89
101
  if (matched.length) {
90
102
  parts.push(`Operations whose name or description matches the question (AUTHORITATIVE — if one matches, the answer MUST name it and cite its line; never answer that the information is missing):\n${matched.map((m) => `- ${m.text}`).join("\n")}`);
91
103
  }
92
104
  parts.push(catalogNote(opRows.map((r) => r.anchor)));
93
- return parts.filter(Boolean).join("\n\n") || undefined;
105
+ const note = parts.filter(Boolean).join("\n\n") || undefined;
106
+ return { note, matched };
94
107
  } catch (e) {
95
108
  // the lane is optional — but never silent (the blind-failure lesson)
96
109
  console.error(`operations-catalog: D1 read failed${retried ? " twice" : ""} — the note is dropped`, String(e).slice(0, 120));