@konneal/engine 0.2.155 → 0.2.157

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-TXF5VMNM.js";
3
+ } from "./chunk-S3O7QZQ7.js";
4
4
  import "./chunk-ECOYO7EX.js";
5
5
  import "./chunk-VYEMFSU5.js";
6
6
  import "./chunk-G2RE4XRV.js";
@@ -2914,7 +2914,25 @@ Answer account questions from these records ONLY: name the record when you use i
2914
2914
  ...h.metadata.table_selection ? { sel: h.metadata.table_selection } : {}
2915
2915
  }));
2916
2916
  const tq = await chargeAnswerTokens(env, bucketId, void 0, messageChars(messages), answer.length, probeTokensUsed);
2917
- return json({ ...out, context: contextOut, read: readAs(), quota: { ...quota, ...tq, credits: await creditBudget(env, bucketId), credit_estimate: creditPreview } }, 200, { ...corsHeaders(req), "server-timing": serverTiming() });
2917
+ const manifestOut = body?.manifest === true ? { manifest: {
2918
+ index_version: env.INDEX_VERSION,
2919
+ corpus_gen: gen,
2920
+ model,
2921
+ effort: answerEffort(env),
2922
+ fresh,
2923
+ citations: used.length,
2924
+ retrieval: used.map((h, i) => ({
2925
+ rank: i + 1,
2926
+ doc_id: h.metadata.doc_id,
2927
+ clause_anchor: h.metadata.clause_anchor ?? null,
2928
+ score: h.score,
2929
+ rerank_score: h.rerank_score ?? null
2930
+ })),
2931
+ stages: stageTiming,
2932
+ generate_retries: generateRetries,
2933
+ total_ms: Date.now() - tStart
2934
+ } } : {};
2935
+ return json({ ...out, context: contextOut, read: readAs(), ...manifestOut, quota: { ...quota, ...tq, credits: await creditBudget(env, bucketId), credit_estimate: creditPreview } }, 200, { ...corsHeaders(req), "server-timing": serverTiming() });
2918
2936
  };
2919
2937
  if (liveResponse) {
2920
2938
  void pipeline(live, q).catch(() => {
@@ -0,0 +1,29 @@
1
+ /** The model's raw response → the situating context: trimmed, the
2
+ * wrapping quotes stripped, capped at 400 chars. Empty string = the
3
+ * model had nothing to say (the deterministic-empty class). */
4
+ export declare function normalizeContext(raw: unknown): string;
5
+ export type EnrichOutcome = {
6
+ action: "serve";
7
+ context: string;
8
+ cached: boolean;
9
+ } | {
10
+ action: "serve-raw";
11
+ } | {
12
+ action: "fail";
13
+ error: string;
14
+ };
15
+ /** The per-chunk decision, given the cache read and the generation.
16
+ * - a context (cached or generated) serves enriched;
17
+ * - an EMPTY generation in the production flow serves the chunk RAW
18
+ * (the deterministic-empty class, 2026-10-20: real corpus content —
19
+ * B 18's thin terms sections, two R 134-1 clauses — must not vanish
20
+ * from the index because its enrichment has nothing to say; a later
21
+ * forced re-enrich overwrites the vector in place);
22
+ * - an empty generation in mode:context / mode:ab FAILS: those
23
+ * builders want the signal, not a side-effecting write. */
24
+ export declare function enrichOutcome(opts: {
25
+ cached: string | null;
26
+ generated: string;
27
+ contextOnly: boolean;
28
+ abMode: boolean;
29
+ }): EnrichOutcome;
@@ -4,7 +4,7 @@ import {
4
4
  handleMemories,
5
5
  scoreFaithfulness,
6
6
  scoreJudge
7
- } from "../../chunk-TXF5VMNM.js";
7
+ } from "../../chunk-S3O7QZQ7.js";
8
8
  import "../../chunk-ECOYO7EX.js";
9
9
  import {
10
10
  TOOLS_REGISTRY,
@@ -351,6 +351,18 @@ async function handleGetShared(env, slug) {
351
351
  // workers/worker_public/prompts/enrichment.md
352
352
  var enrichment_default = "You write a retrieval context for a passage from {{CORPUS_KIND}}. The context is prepended to the passage before embedding so a semantic search can locate the passage even when the query uses different vocabulary than the passage itself.\n\nWrite ONE concise sentence (at most 40 words) that situates the passage: name the publication by its exact {{PUBLISHER_NAME}} identifier (including part or annex when applicable) and what the passage covers \u2014 paraphrasing the topic in words DIFFERENT from the passage's own. Do not copy the passage verbatim, do not add facts that are not derivable from the passage or its header, do not answer or explain the content. Reply with the context sentence only \u2014 no quotes, no preamble.\n";
353
353
 
354
+ // workers/worker_public/src/enrichPolicy.ts
355
+ function normalizeContext(raw) {
356
+ return typeof raw === "string" ? raw.trim().replace(/^["\']|["']$/g, "").slice(0, 400) : "";
357
+ }
358
+ function enrichOutcome(opts) {
359
+ const fromCache = typeof opts.cached === "string" && opts.cached;
360
+ if (fromCache) return { action: "serve", context: fromCache, cached: true };
361
+ if (opts.generated) return { action: "serve", context: opts.generated, cached: false };
362
+ if (opts.contextOnly || opts.abMode) return { action: "fail", error: "empty enrichment" };
363
+ return { action: "serve-raw" };
364
+ }
365
+
354
366
  // workers/worker_public/prompts/doc-summary.md
355
367
  var doc_summary_default = "You write the document-level summary for a legal-metrology publication in {{PUBLISHER_NAME}}'s retrieval index. The section summaries below cover the publication clause by clause. Write one compact abstract, three to five sentences: what the publication regulates or provides, its scope and applicability, and the structure a reader should know (parts, annexes, where the normative core sits). Use the publication's own terms exactly. No preamble, no headings.\n";
356
368
 
@@ -408,10 +420,11 @@ ${c.text.slice(0, 1500)}` }
408
420
  reasoning_effort: abMode ? effort : "high"
409
421
  });
410
422
  const raw = typeof res?.response === "string" && res.response.trim() ? res.response : res?.choices?.[0]?.message?.content;
411
- context = typeof raw === "string" ? raw.trim().replace(/^["\']|[\"']$/g, "").slice(0, 400) : "";
423
+ context = normalizeContext(raw);
412
424
  if (!context) {
413
425
  console.log("enrich raw keys:", Object.keys(res ?? {}), "sample:", JSON.stringify(res).slice(0, 300));
414
- if (contextOnly || abMode) return { id: c.id, ok: false, error: "empty enrichment" };
426
+ const outcome = enrichOutcome({ cached: null, generated: "", contextOnly, abMode });
427
+ if (outcome.action === "fail") return { id: c.id, ok: false, error: outcome.error };
415
428
  const rawText = typeof c.metadata.chunk_text === "string" && c.metadata.chunk_text ? c.metadata.chunk_text : c.text;
416
429
  const rawVector = await embed(portModelRunner(env), MODELS.embed, rawText.slice(0, 6e3));
417
430
  await env.VECTORIZE.upsert([{ id: c.id, values: rawVector, metadata: { ...c.metadata, chunk_text: rawText } }]);
@@ -1111,7 +1124,7 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
1111
1124
  const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
1112
1125
  payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
1113
1126
  } else {
1114
- const res = name === "ask" ? await (await import("../../ask-MRVXR7CY.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-GAU5SEZI.js")).handleSearch(env, ctx, inner, tier, key);
1127
+ const res = name === "ask" ? await (await import("../../ask-IC5A5LIN.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-GAU5SEZI.js")).handleSearch(env, ctx, inner, tier, key);
1115
1128
  payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
1116
1129
  }
1117
1130
  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.155",
30
+ "version": "0.2.157",
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",
@@ -6,6 +6,7 @@ import { embed } from "./ai";
6
6
  import { err, json, corsHeaders, readJson, authenticate } from "./lib/http";
7
7
  import type { Env } from "./env";
8
8
  import enrichmentPrompt from "../prompts/enrichment.md";
9
+ import { normalizeContext, enrichOutcome } from "./enrichPolicy.ts";
9
10
  import docSummaryPrompt from "../prompts/doc-summary.md";
10
11
  import familySummaryPrompt from "../prompts/family-summary.md";
11
12
  import sectionSummaryPrompt from "../prompts/section-summary.md";
@@ -75,19 +76,14 @@ export async function handleEnrich(env: Env, ctx: ExecutionContext, req: Request
75
76
  const raw = typeof res?.response === "string" && res.response.trim()
76
77
  ? res.response
77
78
  : res?.choices?.[0]?.message?.content;
78
- context = typeof raw === "string" ? raw.trim().replace(/^["\']|[\"']$/g, "").slice(0, 400) : "";
79
+ context = normalizeContext(raw);
79
80
  if (!context) {
80
81
  console.log("enrich raw keys:", Object.keys(res ?? {}), "sample:", JSON.stringify(res).slice(0, 300));
81
- // The deterministic-empty class (observed 2026-10-20 on B 18's
82
- // thin terms sections and two R 134-1 clauses — empty under
83
- // BOTH the default and the alternate enrichment model): the
84
- // chunk's own text is real corpus content, so the production
85
- // flow serves it RAW rather than leaving a hole in the index
86
- // — embed the original text without the context band, upsert
87
- // without the ctx marker; a later forced re-enrich overwrites
88
- // the vector in place. mode:"context" and "ab" keep failing:
89
- // their builders want the signal, not a side-effecting write.
90
- if (contextOnly || abMode) return { id: c.id, ok: false, error: "empty enrichment" };
82
+ // serve-raw: the chunk's own text is real corpus content —
83
+ // embed it without the context band, upsert without the ctx
84
+ // marker; a later forced re-enrich overwrites in place.
85
+ const outcome = enrichOutcome({ cached: null, generated: "", contextOnly, abMode });
86
+ if (outcome.action === "fail") return { id: c.id, ok: false, error: outcome.error };
91
87
  const rawText = typeof c.metadata.chunk_text === "string" && c.metadata.chunk_text ? c.metadata.chunk_text : c.text;
92
88
  const rawVector = await embed(portModelRunner(env), MODELS.embed, rawText.slice(0, 6000));
93
89
  await env.VECTORIZE.upsert([{ id: c.id, values: rawVector, metadata: { ...c.metadata, chunk_text: rawText } }]);
@@ -1969,7 +1969,33 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1969
1969
  ...((h.metadata as any).table_selection ? { sel: (h.metadata as any).table_selection } : {}),
1970
1970
  }));
1971
1971
  const tq = await chargeAnswerTokens(env, bucketId, undefined, messageChars(messages!), answer!.length, probeTokensUsed);
1972
- return json({ ...out, context: contextOut, read: readAs(), quota: { ...quota, ...tq, credits: await creditBudget(env, bucketId), credit_estimate: creditPreview } }, 200, { ...corsHeaders(req), "server-timing": serverTiming() });
1972
+ // the reproducibility manifest (TODO.sota/14.6, v1): an opt-in, one-object
1973
+ // derivation record — the served snapshot identity, the answer model, the
1974
+ // retrieval anatomy (rank, corpus score, rerank score per used passage),
1975
+ // the stage timings, and the enforcement counters. Everything here is
1976
+ // already computed; the flag only ASSEMBLES it for the harness and for
1977
+ // external auditors. v2 names the CRAG grades per passage.
1978
+ const manifestOut = body?.manifest === true
1979
+ ? { manifest: {
1980
+ index_version: env.INDEX_VERSION,
1981
+ corpus_gen: gen,
1982
+ model,
1983
+ effort: answerEffort(env),
1984
+ fresh,
1985
+ citations: used.length,
1986
+ retrieval: used.map((h: Hit, i: number) => ({
1987
+ rank: i + 1,
1988
+ doc_id: h.metadata.doc_id,
1989
+ clause_anchor: h.metadata.clause_anchor ?? null,
1990
+ score: h.score,
1991
+ rerank_score: h.rerank_score ?? null,
1992
+ })),
1993
+ stages: stageTiming,
1994
+ generate_retries: generateRetries,
1995
+ total_ms: Date.now() - tStart,
1996
+ } }
1997
+ : {};
1998
+ return json({ ...out, context: contextOut, read: readAs(), ...manifestOut, quota: { ...quota, ...tq, credits: await creditBudget(env, bucketId), credit_estimate: creditPreview } }, 200, { ...corsHeaders(req), "server-timing": serverTiming() });
1973
1999
  }
1974
2000
 
1975
2001
  // THE DISPATCH: the streamed lane's response went out at the top — the
@@ -0,0 +1,40 @@
1
+ // The enrichment route's decision policy, dependency-free so plain node
2
+ // can load it for unit tests (admin.ts imports .md prompts that only
3
+ // the bundler resolves — the serve-raw policy rode the live
4
+ // verification alone until this extraction). The handler keeps the
5
+ // I/O: cache, generation, embedding, upsert; this module owns the
6
+ // DECISION table — what a chunk's outcome is, given what came back.
7
+
8
+ /** The model's raw response → the situating context: trimmed, the
9
+ * wrapping quotes stripped, capped at 400 chars. Empty string = the
10
+ * model had nothing to say (the deterministic-empty class). */
11
+ export function normalizeContext(raw: unknown): string {
12
+ return typeof raw === "string" ? raw.trim().replace(/^["\']|["']$/g, "").slice(0, 400) : "";
13
+ }
14
+
15
+ export type EnrichOutcome =
16
+ | { action: "serve"; context: string; cached: boolean }
17
+ | { action: "serve-raw" }
18
+ | { action: "fail"; error: string };
19
+
20
+ /** The per-chunk decision, given the cache read and the generation.
21
+ * - a context (cached or generated) serves enriched;
22
+ * - an EMPTY generation in the production flow serves the chunk RAW
23
+ * (the deterministic-empty class, 2026-10-20: real corpus content —
24
+ * B 18's thin terms sections, two R 134-1 clauses — must not vanish
25
+ * from the index because its enrichment has nothing to say; a later
26
+ * forced re-enrich overwrites the vector in place);
27
+ * - an empty generation in mode:context / mode:ab FAILS: those
28
+ * builders want the signal, not a side-effecting write. */
29
+ export function enrichOutcome(opts: {
30
+ cached: string | null;
31
+ generated: string;
32
+ contextOnly: boolean;
33
+ abMode: boolean;
34
+ }): EnrichOutcome {
35
+ const fromCache = typeof opts.cached === "string" && opts.cached;
36
+ if (fromCache) return { action: "serve", context: fromCache, cached: true };
37
+ if (opts.generated) return { action: "serve", context: opts.generated, cached: false };
38
+ if (opts.contextOnly || opts.abMode) return { action: "fail", error: "empty enrichment" };
39
+ return { action: "serve-raw" };
40
+ }