@konneal/engine 0.2.171 → 0.2.176

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.
Files changed (34) hide show
  1. package/dist/ablate.d.ts +9 -4
  2. package/dist/admin.d.ts +5 -3
  3. package/dist/ask-ZWZFZBKZ.js +16 -0
  4. package/dist/{chunk-QS6V53RO.js → chunk-B2MU4ZWM.js} +3 -3
  5. package/dist/{chunk-RZLEHCIA.js → chunk-CLDMP5YH.js} +3 -3
  6. package/dist/{chunk-O2XMNKCK.js → chunk-D7MO4RVQ.js} +1 -1
  7. package/dist/{chunk-PQDAL4MX.js → chunk-FNQ7UOEB.js} +1 -1
  8. package/dist/{chunk-5UIMQAJZ.js → chunk-HWWJAJWM.js} +7 -0
  9. package/dist/{chunk-6KEWBP7O.js → chunk-MUWO2EV6.js} +4 -4
  10. package/dist/{chunk-KKJHJXQV.js → chunk-T6T2QUO6.js} +27 -12
  11. package/dist/{chunk-XWSZWFYP.js → chunk-ZHT4456M.js} +1 -1
  12. package/dist/config.d.ts +2 -0
  13. package/dist/decisionWire.d.ts +15 -0
  14. package/dist/openapi-surface.gen.d.ts +1 -1
  15. package/dist/openapi-types.d.ts +54 -1
  16. package/dist/pipeline.d.ts +4 -3
  17. package/dist/search-KNPPTHMG.js +14 -0
  18. package/dist/{tools-HK5RXHBQ.js → tools-VHN54DWH.js} +4 -4
  19. package/dist/worker_mcp/src/index.js +2 -2
  20. package/dist/worker_public/src/config.js +1 -1
  21. package/dist/worker_public/src/index.js +90 -28
  22. package/dist/worker_public/src/requestScope.js +2 -2
  23. package/package.json +1 -1
  24. package/workers/worker_public/openapi.yaml +39 -1
  25. package/workers/worker_public/src/ablate.ts +12 -6
  26. package/workers/worker_public/src/admin.ts +84 -20
  27. package/workers/worker_public/src/ask.ts +14 -7
  28. package/workers/worker_public/src/config.ts +7 -0
  29. package/workers/worker_public/src/decisionWire.ts +25 -0
  30. package/workers/worker_public/src/index.ts +2 -1
  31. package/workers/worker_public/src/openapi-surface.gen.ts +6 -0
  32. package/workers/worker_public/src/pipeline.ts +4 -3
  33. package/dist/ask-4T3E2Z6J.js +0 -16
  34. package/dist/search-RNCRT5BR.js +0 -14
package/dist/ablate.d.ts CHANGED
@@ -13,11 +13,16 @@ export interface AblateConfig {
13
13
  * diversified subset + one strong verifier. Off in serving until the
14
14
  * gate promotes it; the grid arms it per ask. */
15
15
  speculative: boolean;
16
- /** Extractive prompt compression (TODO.sota/02 row 5): each passage
17
- * body is query-weighted sentence selection at a fraction of its
18
- * budget before the answer model reads it. Off in serving; the grid
19
- * arms it per ask (the row's accuracy-at-token-budget gate). */
16
+ /** Extractive prompt compression (TODO.sota/02 row 5): PROMOTED — the
17
+ * passage bodies are query-weighted sentence selection at a fraction
18
+ * of their budget in serving (the row's gate: identical accuracy at
19
+ * ~1/20th the prompt, the whole-document baseline measured it).
20
+ * ablate_compress survives for the grid's explicit arm;
21
+ * ablate_no_compress is the measurement stand-down. */
20
22
  compress: boolean;
23
+ /** Per-request stand-down for the promoted compressor (the
24
+ * measurement arm; the serving default is on). */
25
+ noCompress: boolean;
21
26
  /** The decision pass (TODO.decisions/01): per-request stand-down for
22
27
  * the measurement — the serving switch is DECISIONS_ENABLED. */
23
28
  noDecisions: boolean;
package/dist/admin.d.ts CHANGED
@@ -21,9 +21,11 @@ export declare function handleSectionUnit(env: Env, ctx: ExecutionContext, req:
21
21
  * the deterministic registry line. Admin-gated; idempotent (force
22
22
  * regenerates); the quality-first lane pays once. */
23
23
  export declare function handleFamilySummary(env: Env, req: Request): Promise<Response>;
24
- /** Ops access to the Vectorize binding (get/upsert by id) for offline
25
- * passes like embedding smoothing (G-ETSI-4) — the binding is the
26
- * credential, admin-token gated exactly like /admin/enrich. */
24
+ /** The pairwise vision judge (TODO.sota/18.4's A/B): two anonymized
25
+ * captions of the SAME figure, presented in randomized order beside the
26
+ * image itself; a THIRD model (never a contestant) picks the better
27
+ * caption with a reason. Admin-gated; one-time quality-first lane. */
28
+ export declare function handleVisionJudge(env: Env, req: Request): Promise<Response>;
27
29
  /** One-time figure captioning (TODO.remaining/03): fetch the unit's asset
28
30
  * from R2, describe it with the vision-capable answer model, store the
29
31
  * description into unit_payloads. Admin-gated; idempotent. */
@@ -0,0 +1,16 @@
1
+ import {
2
+ handleAsk
3
+ } from "./chunk-T6T2QUO6.js";
4
+ import "./chunk-ECOYO7EX.js";
5
+ import "./chunk-B2MU4ZWM.js";
6
+ import "./chunk-CLDMP5YH.js";
7
+ import "./chunk-ZHT4456M.js";
8
+ import "./chunk-D7MO4RVQ.js";
9
+ import "./chunk-ETLSWSZ7.js";
10
+ import "./chunk-FNQ7UOEB.js";
11
+ import "./chunk-HWWJAJWM.js";
12
+ import "./chunk-3FYJM7LH.js";
13
+ import "./chunk-KINFFYQH.js";
14
+ export {
15
+ handleAsk
16
+ };
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  embed
3
- } from "./chunk-XWSZWFYP.js";
3
+ } from "./chunk-ZHT4456M.js";
4
4
  import {
5
5
  standardKeysFrom
6
- } from "./chunk-PQDAL4MX.js";
6
+ } from "./chunk-FNQ7UOEB.js";
7
7
  import {
8
8
  THRESHOLDS
9
- } from "./chunk-5UIMQAJZ.js";
9
+ } from "./chunk-HWWJAJWM.js";
10
10
  import {
11
11
  P
12
12
  } from "./chunk-3FYJM7LH.js";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  embed,
3
3
  rerank
4
- } from "./chunk-XWSZWFYP.js";
4
+ } from "./chunk-ZHT4456M.js";
5
5
  import {
6
6
  bubbleConfirmPage,
7
7
  isAllowedBubbleOrigin
8
- } from "./chunk-O2XMNKCK.js";
8
+ } from "./chunk-D7MO4RVQ.js";
9
9
  import {
10
10
  DATASETS,
11
11
  LIMITS,
@@ -15,7 +15,7 @@ import {
15
15
  processExpansion,
16
16
  sha256Hex,
17
17
  today
18
- } from "./chunk-5UIMQAJZ.js";
18
+ } from "./chunk-HWWJAJWM.js";
19
19
  import {
20
20
  P
21
21
  } from "./chunk-3FYJM7LH.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LIMITS,
3
3
  sha256Hex
4
- } from "./chunk-5UIMQAJZ.js";
4
+ } from "./chunk-HWWJAJWM.js";
5
5
  import {
6
6
  P
7
7
  } from "./chunk-3FYJM7LH.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DATASETS,
3
3
  datasetAllowed
4
- } from "./chunk-5UIMQAJZ.js";
4
+ } from "./chunk-HWWJAJWM.js";
5
5
  import {
6
6
  P
7
7
  } from "./chunk-3FYJM7LH.js";
@@ -11,6 +11,13 @@ var MODELS = {
11
11
  // Vision image-parts land with answer contract v2; text answers work now.
12
12
  anon: "@cf/zai-org/glm-5.3-flash",
13
13
  member: "@cf/zai-org/glm-5.3-flash",
14
+ // figure captions + the vision lane (one-time, quality-first; the A/B
15
+ // lever is CAPTION_MODEL, the per-request arm is the admin route's
16
+ // body.model)
17
+ caption: "@cf/zai-org/glm-5.3-flash",
18
+ // the pairwise vision judge (sees the image; a THIRD model never
19
+ // judges its own arm)
20
+ visionJudge: "@cf/deepseek-ai/deepseek-v4.1-flash-1017",
14
21
  // the VLM figure rerank (TODO.sota/18.3): the newly-landed
15
22
  // vision-capable MoE scores figure IMAGES against figure-seeking
16
23
  // questions — one bounded call per figure question, ablatable stage
@@ -6,24 +6,24 @@ import {
6
6
  sessionFrom,
7
7
  telemetry,
8
8
  understandQuery
9
- } from "./chunk-RZLEHCIA.js";
9
+ } from "./chunk-CLDMP5YH.js";
10
10
  import {
11
11
  corsHeaders,
12
12
  err,
13
13
  json,
14
14
  readJson,
15
15
  validateQuery
16
- } from "./chunk-O2XMNKCK.js";
16
+ } from "./chunk-D7MO4RVQ.js";
17
17
  import {
18
18
  entitlementScope,
19
19
  standardKeysFrom
20
- } from "./chunk-PQDAL4MX.js";
20
+ } from "./chunk-FNQ7UOEB.js";
21
21
  import {
22
22
  LIMITS,
23
23
  MODELS,
24
24
  num,
25
25
  sha256Hex
26
- } from "./chunk-5UIMQAJZ.js";
26
+ } from "./chunk-HWWJAJWM.js";
27
27
  import {
28
28
  portModelRunner
29
29
  } from "./chunk-KINFFYQH.js";
@@ -32,7 +32,7 @@ import {
32
32
  tableRetyped,
33
33
  toolNote,
34
34
  verdictNote
35
- } from "./chunk-QS6V53RO.js";
35
+ } from "./chunk-B2MU4ZWM.js";
36
36
  import {
37
37
  NO_CONTEXT,
38
38
  STAGE_NAMES,
@@ -75,18 +75,18 @@ import {
75
75
  tokenBudget,
76
76
  understandQuery,
77
77
  usageTotal
78
- } from "./chunk-RZLEHCIA.js";
78
+ } from "./chunk-CLDMP5YH.js";
79
79
  import {
80
80
  embed,
81
81
  generateOnce
82
- } from "./chunk-XWSZWFYP.js";
82
+ } from "./chunk-ZHT4456M.js";
83
83
  import {
84
84
  corsHeaders,
85
85
  err,
86
86
  json,
87
87
  readJson,
88
88
  validateQuery
89
- } from "./chunk-O2XMNKCK.js";
89
+ } from "./chunk-D7MO4RVQ.js";
90
90
  import {
91
91
  cacheableAnswer,
92
92
  canonicalRefusal,
@@ -99,7 +99,7 @@ import {
99
99
  requestSalt,
100
100
  resolveRequestScope,
101
101
  standardKeysFrom
102
- } from "./chunk-PQDAL4MX.js";
102
+ } from "./chunk-FNQ7UOEB.js";
103
103
  import {
104
104
  LIMITS,
105
105
  MODELS,
@@ -110,7 +110,7 @@ import {
110
110
  requestEffort,
111
111
  roleModel,
112
112
  sha256Hex
113
- } from "./chunk-5UIMQAJZ.js";
113
+ } from "./chunk-HWWJAJWM.js";
114
114
  import {
115
115
  P
116
116
  } from "./chunk-3FYJM7LH.js";
@@ -1404,10 +1404,11 @@ function parseAblate(body, adminToken, presentedToken, knownStages) {
1404
1404
  const rawRoute = body?.ablate_route;
1405
1405
  const speculative = body?.ablate_speculative === true;
1406
1406
  const compress = body?.ablate_compress === true;
1407
+ const noCompress = body?.ablate_no_compress === true;
1407
1408
  const noDecisions = body?.ablate_no_decisions === true;
1408
1409
  const namesAblation = rawStages !== void 0 && rawStages !== null;
1409
1410
  const routeAblation = rawRoute !== void 0 && rawRoute !== null && rawRoute !== "adaptive";
1410
- if (!namesAblation && !noVerdict && !routeAblation && !speculative && !compress && !noDecisions) return { ok: true, config: null };
1411
+ if (!namesAblation && !noVerdict && !routeAblation && !speculative && !compress && !noCompress && !noDecisions) return { ok: true, config: null };
1411
1412
  if (!adminToken || presentedToken !== adminToken) {
1412
1413
  return {
1413
1414
  ok: false,
@@ -1438,7 +1439,7 @@ function parseAblate(body, adminToken, presentedToken, knownStages) {
1438
1439
  }
1439
1440
  stages = [...new Set(rawStages)];
1440
1441
  }
1441
- return { ok: true, config: { stages, noVerdict, route, speculative, compress, noDecisions } };
1442
+ return { ok: true, config: { stages, noVerdict, route, speculative, compress, noCompress, noDecisions } };
1442
1443
  }
1443
1444
 
1444
1445
  // workers/worker_public/src/decisions.ts
@@ -1621,6 +1622,15 @@ ${passages.join("\n")}`.slice(0, 12e3),
1621
1622
  return null;
1622
1623
  }
1623
1624
  }
1625
+ function telemetryDecisions(env, ctx, tier, queryHash, result, durationMs) {
1626
+ try {
1627
+ telemetry(env, ctx, tier, "ask-decisions", result ? result.gate.toFixed(2) : "0", !!result, result?.items.length ?? 0, queryHash, result?.mode ?? void 0, void 0, { durationMs });
1628
+ for (const it of result?.items ?? []) {
1629
+ telemetry(env, ctx, tier, "ask-decision", it.probability.toFixed(2), it.band === "met", 1, queryHash, it.band, void 0, {});
1630
+ }
1631
+ } catch {
1632
+ }
1633
+ }
1624
1634
 
1625
1635
  // workers/worker_public/src/route.ts
1626
1636
  var ROUTE_FAST_WORD_CAP = 10;
@@ -2828,7 +2838,7 @@ Answer account questions from these records ONLY: name the record when you use i
2828
2838
  [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,
2829
2839
  summary,
2830
2840
  budget,
2831
- ablate?.compress === true
2841
+ ablate?.noCompress !== true
2832
2842
  );
2833
2843
  const decisionPromise = decisionsArmed && decisionsGatePromise ? decisionsGatePromise.then((g) => g != null && g >= THRESHOLDS.decisionsGateMin ? runDecisionPass(env, {
2834
2844
  question: q2.query,
@@ -2976,14 +2986,17 @@ Answer account questions from these records ONLY: name the record when you use i
2976
2986
  let canonical0 = exciseMisplacedPin(canonicalRefusal(full.replace(/^TOOL [a-z_.]+.*$\n?/gm, "").replace(/^\n+/, ""), refusalScope(answerCites(full))), answerCites(full));
2977
2987
  const c2 = canonical0.includes(refusalAnswer()) ? { text: canonical0, blocks: [], dropped: [] } : await contractV2(env.DB, canonical0, usedHits);
2978
2988
  let streamedCompletionBlocks = [];
2979
- if (decisionPromise && !canonical0.includes(refusalAnswer())) {
2989
+ let streamedDecisions;
2990
+ if (decisionPromise && !canonical0.includes(refusalAnswer()) && !verdictBlock && !conditionBlock && !aggregationBlock) {
2980
2991
  const dp = await decisionPromise;
2981
2992
  if (dp) {
2982
2993
  canonical0 += dp.section;
2983
2994
  send({ type: "token", v: dp.section });
2984
2995
  full += dp.section;
2996
+ streamedDecisions = { mode: dp.mode, gate: dp.gate, items: dp.items };
2985
2997
  console.log("decisions: streamed the checklist section (" + dp.items.length + " items, mode " + dp.mode + ")");
2986
2998
  }
2999
+ telemetryDecisions(env, ctx, tier, queryHash, streamedDecisions ?? null, Date.now() - tStart);
2987
3000
  }
2988
3001
  if (!canonical0.includes(refusalAnswer())) {
2989
3002
  try {
@@ -2998,6 +3011,7 @@ Answer account questions from these records ONLY: name the record when you use i
2998
3011
  model,
2999
3012
  query_hash: queryHash,
3000
3013
  follow_ups: refusalShaped(canonical0, refusalScope(answerCites(full))) ? [] : understanding?.follow_ups ?? [],
3014
+ ...streamedDecisions ? { decisions: streamedDecisions } : {},
3001
3015
  blocks: [...c2.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...aggregationBlock ? [aggregationBlock] : [], ...streamedCompletionBlocks],
3002
3016
  context_applied: ctxApplied,
3003
3017
  read: readAs(),
@@ -3162,7 +3176,7 @@ Answer account questions from these records ONLY: name the record when you use i
3162
3176
  ...routeSubset ? { stageSubset: routeSubset ?? void 0 } : {}
3163
3177
  });
3164
3178
  if (retryRetrieve.hits.length > 0) {
3165
- const { messages: retryMessages, usedHits: retryUsed } = buildMessages(q2.query, retryRetrieve.hits, q2.lang, keptHistory, void 0, summary, budget, ablate?.compress === true);
3179
+ const { messages: retryMessages, usedHits: retryUsed } = buildMessages(q2.query, retryRetrieve.hits, q2.lang, keptHistory, void 0, summary, budget, ablate?.noCompress !== true);
3166
3180
  const retryAnswer = await generateOnce(env, model, retryMessages, routeEffort(routed.route));
3167
3181
  if (retryAnswer) {
3168
3182
  answer = canonicalRefusal(retryAnswer, refusalScope(answerCites(retryAnswer)));
@@ -3219,10 +3233,11 @@ Answer account questions from these records ONLY: name the record when you use i
3219
3233
  let decisionsOut;
3220
3234
  if (decisionPromise) {
3221
3235
  const dp = await decisionPromise;
3222
- if (dp && answer && !answer.includes(refusalAnswer())) {
3236
+ if (dp && answer && !answer.includes(refusalAnswer()) && !verdictBlock && !conditionBlock && !aggregationBlock) {
3223
3237
  answer = answer + dp.section;
3224
3238
  decisionsOut = { mode: dp.mode, gate: dp.gate, items: dp.items };
3225
3239
  }
3240
+ telemetryDecisions(env, ctx, tier, queryHash, decisionsOut ?? null, Date.now() - tStart);
3226
3241
  }
3227
3242
  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: refusalShaped(answer, refusalScope(answerCites(answer))) ? [] : understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...aggregationBlock ? [aggregationBlock] : [], ...registerBlock ? [registerBlock] : [], ...completionBlocks], context_applied: ctxApplied, ...liveRecords ? { records: liveRecords } : {}, ...decisionsOut ? { decisions: decisionsOut } : {}, ...ablate ? { ablate } : {}, route: { route: routed.route, features: routed.features } };
3228
3243
  const cacheable = !contextual && !declaredCtx && !ablate && cacheableAnswer(answer, refusalScope(answerCites(answer))) && finalAnchors.violations.length === 0;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  answerEffort,
3
3
  effortBudget
4
- } from "./chunk-5UIMQAJZ.js";
4
+ } from "./chunk-HWWJAJWM.js";
5
5
 
6
6
  // workers/worker_public/src/ai.ts
7
7
  var delay = (ms) => new Promise((r) => setTimeout(r, ms));
package/dist/config.d.ts CHANGED
@@ -3,6 +3,8 @@ export declare const MODELS: {
3
3
  readonly rerank: "@cf/baai/bge-reranker-base";
4
4
  readonly anon: "@cf/zai-org/glm-5.3-flash";
5
5
  readonly member: "@cf/zai-org/glm-5.3-flash";
6
+ readonly caption: "@cf/zai-org/glm-5.3-flash";
7
+ readonly visionJudge: "@cf/deepseek-ai/deepseek-v4.1-flash-1017";
6
8
  readonly vlmFigure: "@cf/deepseek-ai/deepseek-v4.1-flash-1017";
7
9
  readonly decisionsGate: "@cf/cloudflare/clef-flash";
8
10
  readonly decisionsModel: "@cf/cloudflare/clef";
@@ -23,3 +23,18 @@ export declare function runDecisionPass(env: Env, opts: {
23
23
  statedValues?: string;
24
24
  userDocs?: boolean;
25
25
  }): Promise<DecisionPassResult | null>;
26
+ /** The decisions telemetry (TODO.decisions/03): the nightly's
27
+ * calibration signal, riding the queries table's route key with a
28
+ * defined encoding — no schema migration.
29
+ * - one PASS row, route "ask-decisions": ok = fired, lang = mode,
30
+ * model = the gate probability (2dp), duration_ms = the pass's.
31
+ * - one ITEM row per requirement, route "ask-decision": ok = band is
32
+ * "met", lang = band, model = the probability (2dp); query_hash
33
+ * joins the rows to their pass. */
34
+ export declare function telemetryDecisions(env: Env, ctx: {
35
+ waitUntil(p: Promise<unknown>): void;
36
+ }, tier: string, queryHash: string, result: {
37
+ mode: string;
38
+ gate: number;
39
+ items: DecisionItem[];
40
+ } | null, durationMs: number): void;
@@ -3,7 +3,7 @@ export interface OpenApiRoute {
3
3
  pattern: string;
4
4
  operationId: string;
5
5
  }
6
- export type OpenApiOperationId = "credits" | "creditsAlias" | "askAnonymous" | "askKeyed" | "search" | "searchKeyed" | "verify" | "verifyKeyed" | "research" | "researchKeyed" | "mcp" | "datasets" | "keyUsage" | "health" | "listConversations" | "createConversation" | "getConversation" | "renameConversation" | "deleteConversation" | "appendMessage" | "shareConversation" | "getShared" | "uploadAttachment" | "listAttachments" | "getAttachment" | "deleteAttachment" | "listMemories" | "createMemory" | "deleteMemory" | "listProjects" | "createProject" | "deleteProject" | "listProjectFiles" | "attachProjectFile" | "detachProjectFile" | "laneQuery" | "laneKeyed" | "feedback" | "adminEnrich" | "adminEnrichAlias" | "adminSection" | "adminFamilySummary" | "adminSectionAlias" | "adminFamilySummaryAlias" | "adminVectors" | "adminCaption" | "adminJudge" | "adminJudgeAlias" | "adminRevokeKey" | "authMe" | "authLogin" | "authCallback" | "authLogout" | "authLogoutLink" | "adminStats" | "adminListKeys" | "adminCreateKey";
6
+ export type OpenApiOperationId = "credits" | "creditsAlias" | "askAnonymous" | "askKeyed" | "search" | "searchKeyed" | "verify" | "verifyKeyed" | "research" | "researchKeyed" | "mcp" | "datasets" | "keyUsage" | "health" | "listConversations" | "createConversation" | "getConversation" | "renameConversation" | "deleteConversation" | "appendMessage" | "shareConversation" | "getShared" | "uploadAttachment" | "listAttachments" | "getAttachment" | "deleteAttachment" | "listMemories" | "createMemory" | "deleteMemory" | "listProjects" | "createProject" | "deleteProject" | "listProjectFiles" | "attachProjectFile" | "detachProjectFile" | "laneQuery" | "laneKeyed" | "feedback" | "adminEnrich" | "adminEnrichAlias" | "adminSection" | "adminFamilySummary" | "adminSectionAlias" | "adminFamilySummaryAlias" | "adminVectors" | "adminCaption" | "adminVisionJudge" | "adminJudge" | "adminJudgeAlias" | "adminRevokeKey" | "authMe" | "authLogin" | "authCallback" | "authLogout" | "authLogoutLink" | "adminStats" | "adminListKeys" | "adminCreateKey";
7
7
  export declare const OPENAPI_SURFACE: readonly (Omit<OpenApiRoute, "operationId"> & {
8
8
  operationId: OpenApiOperationId;
9
9
  })[];
@@ -753,6 +753,26 @@ export interface paths {
753
753
  patch?: never;
754
754
  trace?: never;
755
755
  };
756
+ "/admin/vision_judge": {
757
+ parameters: {
758
+ query?: never;
759
+ header?: never;
760
+ path?: never;
761
+ cookie?: never;
762
+ };
763
+ get?: never;
764
+ put?: never;
765
+ /**
766
+ * Judge two captions of one figure against the image
767
+ * @description The caption A/B's pairwise judge — two anonymized descriptions of the same figure, presented in randomized order beside the image itself, decided by a third model that took no part in writing either. The vision A/B harness calls this lane.
768
+ */
769
+ post: operations["adminVisionJudge"];
770
+ delete?: never;
771
+ options?: never;
772
+ head?: never;
773
+ patch?: never;
774
+ trace?: never;
775
+ };
756
776
  "/v1/admin/judge": {
757
777
  parameters: {
758
778
  query?: never;
@@ -2164,11 +2184,44 @@ export interface operations {
2164
2184
  unit_id: string;
2165
2185
  /** @description Surrounding clause text, at most 400 characters, to steer the description. */
2166
2186
  context?: string;
2187
+ /** @description Per-request vision model override (an @cf/ id) — the caption A/B's measurement arm. Absent, the CAPTION_MODEL role (default glm-5.3-flash) serves. */
2188
+ model?: string;
2189
+ /** @description Return the description without storing it into the unit's payload. */
2190
+ dry_run?: boolean;
2191
+ };
2192
+ };
2193
+ };
2194
+ responses: {
2195
+ /** @description The description (stored, or returned only under dry_run). */
2196
+ 200: {
2197
+ headers: {
2198
+ [name: string]: unknown;
2199
+ };
2200
+ content?: never;
2201
+ };
2202
+ };
2203
+ };
2204
+ adminVisionJudge: {
2205
+ parameters: {
2206
+ query?: never;
2207
+ header?: never;
2208
+ path?: never;
2209
+ cookie?: never;
2210
+ };
2211
+ requestBody: {
2212
+ content: {
2213
+ "application/json": {
2214
+ /** @description The figure unit whose asset the captions describe. */
2215
+ unit_id: string;
2216
+ captions: {
2217
+ a: string;
2218
+ b: string;
2219
+ };
2167
2220
  };
2168
2221
  };
2169
2222
  };
2170
2223
  responses: {
2171
- /** @description The stored description. */
2224
+ /** @description The winning arm (a or b) with the judge's reason. */
2172
2225
  200: {
2173
2226
  headers: {
2174
2227
  [name: string]: unknown;
@@ -51,9 +51,10 @@ export declare function splitHistory(history: HistoryTurn[], budgetTokens: numbe
51
51
  * incoming order (timeout/parse failure never blocks serving). */
52
52
  export declare function listwiseRerank(env: any, model: string, query: string, hits: Hit[]): Promise<Hit[] | null>;
53
53
  export declare function buildMessages(query: string, hits: Hit[], lang?: string, history?: HistoryTurn[], retrievalNote?: string, conversationSummary?: string, budgetTokens?: number,
54
- /** the extractive compression arm (TODO.sota/02 row 5): ablate_compress
55
- * arms it per ask — each passage body becomes query-weighted sentence
56
- * selection at compressShare of its budget. Off in serving. */
54
+ /** the extractive compression (TODO.sota/02 row 5): PROMOTED — on in
55
+ * serving; ablate_no_compress stands it down per ask for the
56
+ * measurement. Each passage body becomes query-weighted sentence
57
+ * selection at compressShare of its budget. */
57
58
  compress?: boolean): BuiltMessages;
58
59
  export declare function citations(hits: Hit[]): {
59
60
  doc_id: string;
@@ -0,0 +1,14 @@
1
+ import {
2
+ handleSearch
3
+ } from "./chunk-MUWO2EV6.js";
4
+ import "./chunk-CLDMP5YH.js";
5
+ import "./chunk-ZHT4456M.js";
6
+ import "./chunk-D7MO4RVQ.js";
7
+ import "./chunk-ETLSWSZ7.js";
8
+ import "./chunk-FNQ7UOEB.js";
9
+ import "./chunk-HWWJAJWM.js";
10
+ import "./chunk-3FYJM7LH.js";
11
+ import "./chunk-KINFFYQH.js";
12
+ export {
13
+ handleSearch
14
+ };
@@ -4,10 +4,10 @@ import {
4
4
  parseToolCall,
5
5
  runTool,
6
6
  toolNote
7
- } from "./chunk-QS6V53RO.js";
8
- import "./chunk-XWSZWFYP.js";
9
- import "./chunk-PQDAL4MX.js";
10
- import "./chunk-5UIMQAJZ.js";
7
+ } from "./chunk-B2MU4ZWM.js";
8
+ import "./chunk-ZHT4456M.js";
9
+ import "./chunk-FNQ7UOEB.js";
10
+ import "./chunk-HWWJAJWM.js";
11
11
  import "./chunk-3FYJM7LH.js";
12
12
  import "./chunk-KINFFYQH.js";
13
13
  export {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  authenticate
3
- } from "../../chunk-O2XMNKCK.js";
4
- import "../../chunk-5UIMQAJZ.js";
3
+ } from "../../chunk-D7MO4RVQ.js";
4
+ import "../../chunk-HWWJAJWM.js";
5
5
  import {
6
6
  P,
7
7
  setProfile
@@ -16,7 +16,7 @@ import {
16
16
  roleModel,
17
17
  sha256Hex,
18
18
  today
19
- } from "../../chunk-5UIMQAJZ.js";
19
+ } from "../../chunk-HWWJAJWM.js";
20
20
  import "../../chunk-3FYJM7LH.js";
21
21
  export {
22
22
  DATASETS,
@@ -4,16 +4,16 @@ import {
4
4
  handleMemories,
5
5
  scoreFaithfulness,
6
6
  scoreJudge
7
- } from "../../chunk-KKJHJXQV.js";
7
+ } from "../../chunk-T6T2QUO6.js";
8
8
  import "../../chunk-ECOYO7EX.js";
9
9
  import {
10
10
  TOOLS_REGISTRY,
11
11
  bindModelNode,
12
12
  modelGroundingBlock
13
- } from "../../chunk-QS6V53RO.js";
13
+ } from "../../chunk-B2MU4ZWM.js";
14
14
  import {
15
15
  handleSearch
16
- } from "../../chunk-6KEWBP7O.js";
16
+ } from "../../chunk-MUWO2EV6.js";
17
17
  import {
18
18
  buildMessages,
19
19
  citations,
@@ -38,11 +38,11 @@ import {
38
38
  sessionFrom,
39
39
  telemetry,
40
40
  understandQuery
41
- } from "../../chunk-RZLEHCIA.js";
41
+ } from "../../chunk-CLDMP5YH.js";
42
42
  import {
43
43
  embed,
44
44
  generateOnce
45
- } from "../../chunk-XWSZWFYP.js";
45
+ } from "../../chunk-ZHT4456M.js";
46
46
  import {
47
47
  authenticate,
48
48
  corsHeaders,
@@ -51,14 +51,14 @@ import {
51
51
  readJson,
52
52
  validateQuery,
53
53
  withCors
54
- } from "../../chunk-O2XMNKCK.js";
54
+ } from "../../chunk-D7MO4RVQ.js";
55
55
  import {
56
56
  canonicalRefusal
57
57
  } from "../../chunk-ETLSWSZ7.js";
58
58
  import {
59
59
  entitlementScope,
60
60
  standardKeysFrom
61
- } from "../../chunk-PQDAL4MX.js";
61
+ } from "../../chunk-FNQ7UOEB.js";
62
62
  import {
63
63
  LIMITS,
64
64
  MODELS,
@@ -69,7 +69,7 @@ import {
69
69
  roleModel,
70
70
  sha256Hex,
71
71
  today
72
- } from "../../chunk-5UIMQAJZ.js";
72
+ } from "../../chunk-HWWJAJWM.js";
73
73
  import {
74
74
  P,
75
75
  setProfile
@@ -581,6 +581,70 @@ ${chain || "(no successor edges recorded)"}` }
581
581
  return err(502, "family_summary_failed", String(e?.message ?? e).slice(0, 200));
582
582
  }
583
583
  }
584
+ async function loadUnitAsset(env, unitId) {
585
+ const row = await env.DB.prepare("SELECT payload, docidentifier FROM unit_payloads WHERE unit_id = ?1").bind(unitId).first();
586
+ if (!row) return { error: err(404, "not_found", "no unit_payload row for that id") };
587
+ const payload = JSON.parse(String(row.payload));
588
+ const uri = payload.uri ?? "";
589
+ const m = uri.match(/^\/assets\/(.+)/);
590
+ if (!m) return { error: err(400, "invalid_input", "payload has no /assets/ uri (upload the asset first)") };
591
+ const obj = await env.UNIT_ASSETS.get(m[1]);
592
+ if (!obj) return { error: err(404, "not_found", `asset ${m[1]} not in R2`) };
593
+ const buf = await obj.arrayBuffer();
594
+ const ext = m[1].split(".").pop()?.toLowerCase() ?? "png";
595
+ const mime = ext === "svg" ? "image/svg+xml" : `image/${ext === "jpg" ? "jpeg" : ext}`;
596
+ const bytes = new Uint8Array(buf);
597
+ let binary = "";
598
+ for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
599
+ return { row, b64: btoa(binary), mime };
600
+ }
601
+ async function handleVisionJudge(env, req) {
602
+ if (!env.ADMIN_TOKEN) return err(501, "admin_disabled", "ADMIN_TOKEN secret is not configured");
603
+ const auth = req.headers.get("authorization") ?? "";
604
+ if (auth !== `Bearer ${env.ADMIN_TOKEN}`) return err(401, "unauthorized", "Invalid admin token");
605
+ const body = await readJson(req);
606
+ const unitId = typeof body?.unit_id === "string" ? body.unit_id : "";
607
+ const capA = typeof body?.captions?.a === "string" ? body.captions.a.slice(0, 2e3) : "";
608
+ const capB = typeof body?.captions?.b === "string" ? body.captions.b.slice(0, 2e3) : "";
609
+ if (!unitId || !capA || !capB) return err(400, "invalid_input", "unit_id and captions.a/.b required");
610
+ try {
611
+ const loaded = await loadUnitAsset(env, unitId);
612
+ if (loaded.error) return loaded.error;
613
+ const swap = Math.random() < 0.5;
614
+ const first = swap ? capB : capA;
615
+ const second = swap ? capA : capB;
616
+ const res = await env.AI.run(roleModel(env, "visionJudge"), {
617
+ messages: [
618
+ {
619
+ role: "user",
620
+ content: [
621
+ { type: "text", text: `You are judging two descriptions of the same figure (from ${loaded.row.docidentifier}) written for a reader who cannot see it. The figure is attached.
622
+
623
+ Description 1: ${first}
624
+
625
+ Description 2: ${second}
626
+
627
+ Which description better tells the reader what the figure shows and its normative point? Reply with ONLY JSON: {"better": 1 or 2, "reason": "one sentence"}.` },
628
+ { type: "image_url", image_url: { url: `data:${loaded.mime};base64,${loaded.b64}` } }
629
+ ]
630
+ }
631
+ ],
632
+ // the DeepSeek law: reasoning is always on — a 2048 budget starved
633
+ // the verdict to empty content (the E.2.2.x judges failed on
634
+ // "Unexpected end of JSON input"); 4096 leaves the JSON intact
635
+ max_tokens: 4096
636
+ });
637
+ const anyRes = res;
638
+ const text = typeof anyRes?.response === "string" ? anyRes.response : anyRes?.choices?.[0]?.message?.content ?? "";
639
+ const parsed = JSON.parse(text.replace(/^[^\{]*/, "").replace(/[^\}]*$/, ""));
640
+ const said = parsed?.better === 1 ? "first" : parsed?.better === 2 ? "second" : null;
641
+ if (!said) return err(502, "judge_failed", "no decision in the judge's reply: " + text.slice(0, 120));
642
+ const winner = said === "first" === swap ? "b" : "a";
643
+ return json({ ok: true, unit_id: unitId, better: winner, judge: roleModel(env, "visionJudge"), reason: String(parsed.reason ?? "").slice(0, 500) });
644
+ } catch (e) {
645
+ return err(502, "vision_judge_failed", String(e).slice(0, 200));
646
+ }
647
+ }
584
648
  async function handleCaption(env, req) {
585
649
  if (!env.ADMIN_TOKEN) return err(501, "admin_disabled", "ADMIN_TOKEN secret is not configured");
586
650
  const auth = req.headers.get("authorization") ?? "";
@@ -588,27 +652,19 @@ async function handleCaption(env, req) {
588
652
  const body = await readJson(req);
589
653
  const unitId = typeof body?.unit_id === "string" ? body.unit_id : "";
590
654
  const context = typeof body?.context === "string" ? body.context.slice(0, 400) : "";
655
+ const override = typeof body?.model === "string" && body.model.startsWith("@cf/") ? body.model : null;
656
+ const dryRun = body?.dry_run === true;
591
657
  if (!unitId) return err(400, "invalid_input", "unit_id required");
592
658
  try {
593
- const row = await env.DB.prepare("SELECT payload, docidentifier FROM unit_payloads WHERE unit_id = ?1").bind(unitId).first();
594
- if (!row) return err(404, "not_found", "no unit_payload row for that id");
595
- const payload = JSON.parse(String(row.payload));
596
- const uri = payload.uri ?? "";
597
- const m = uri.match(/^\/assets\/(.+)/);
598
- if (!m) return err(400, "invalid_input", "payload has no /assets/ uri (upload the asset first)");
599
- const obj = await env.UNIT_ASSETS.get(m[1]);
600
- if (!obj) return err(404, "not_found", `asset ${m[1]} not in R2`);
601
- const buf = await obj.arrayBuffer();
602
- const ext = m[1].split(".").pop()?.toLowerCase() ?? "png";
603
- const mime = ext === "svg" ? "image/svg+xml" : `image/${ext === "jpg" ? "jpeg" : ext}`;
604
- const bytes = new Uint8Array(buf);
605
- let binary = "";
606
- for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
607
- const b64 = btoa(binary);
659
+ const loaded = await loadUnitAsset(env, unitId);
660
+ if (loaded.error) return loaded.error;
661
+ const row = loaded.row;
662
+ const b64 = loaded.b64;
663
+ const mime = loaded.mime;
608
664
  let res = null;
609
665
  for (let attempt = 0; attempt < 2 && !res; attempt++) {
610
666
  try {
611
- res = await env.AI.run(MODELS.member, {
667
+ res = await env.AI.run(override ?? roleModel(env, "caption"), {
612
668
  messages: [
613
669
  {
614
670
  role: "user",
@@ -636,8 +692,8 @@ async function handleCaption(env, req) {
636
692
  const end = Math.max(cut.lastIndexOf(". "), cut.lastIndexOf("! "), cut.lastIndexOf("? "));
637
693
  return end === -1 ? cut.slice(0, cut.lastIndexOf(" ")) : cut.slice(0, end + 1);
638
694
  })();
639
- await env.DB.prepare("UPDATE unit_payloads SET payload = json_set(payload, '$.description', ?1) WHERE unit_id = ?2").bind(desc, unitId).run();
640
- return json({ ok: true, unit_id: unitId, description: desc });
695
+ if (!dryRun) await env.DB.prepare("UPDATE unit_payloads SET payload = json_set(payload, '$.description', ?1) WHERE unit_id = ?2").bind(desc, unitId).run();
696
+ return json({ ok: true, unit_id: unitId, model: override ?? roleModel(env, "caption"), dry_run: dryRun || void 0, description: desc });
641
697
  } catch (e) {
642
698
  return err(502, "caption_failed", String(e).slice(0, 200));
643
699
  }
@@ -1119,13 +1175,13 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
1119
1175
  // stream:false forces the JSON lane (anon defaults to SSE)
1120
1176
  body: JSON.stringify({ ...args, stream: false })
1121
1177
  });
1122
- const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-HK5RXHBQ.js");
1178
+ const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-VHN54DWH.js");
1123
1179
  let payload;
1124
1180
  if (TOOLS_REGISTRY2.some((t) => t.name === name && t.audiences.includes("mcp"))) {
1125
1181
  const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
1126
1182
  payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
1127
1183
  } else {
1128
- const res = name === "ask" ? await (await import("../../ask-4T3E2Z6J.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-RNCRT5BR.js")).handleSearch(env, ctx, inner, tier, key);
1184
+ const res = name === "ask" ? await (await import("../../ask-ZWZFZBKZ.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-KNPPTHMG.js")).handleSearch(env, ctx, inner, tier, key);
1129
1185
  payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
1130
1186
  }
1131
1187
  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 });
@@ -1392,6 +1448,11 @@ var OPENAPI_SURFACE = [
1392
1448
  "pattern": "/admin/caption",
1393
1449
  "operationId": "adminCaption"
1394
1450
  },
1451
+ {
1452
+ "method": "POST",
1453
+ "pattern": "/admin/vision_judge",
1454
+ "operationId": "adminVisionJudge"
1455
+ },
1395
1456
  {
1396
1457
  "method": "POST",
1397
1458
  "pattern": "/v1/admin/judge",
@@ -1933,6 +1994,7 @@ var OPENAPI_HANDLERS = {
1933
1994
  adminSectionAlias: (c) => handleSectionUnit(c.env, c.ctx, c.req),
1934
1995
  adminVectors: (c) => handleVectors(c.env, c.req),
1935
1996
  adminCaption: (c) => handleCaption(c.env, c.req),
1997
+ adminVisionJudge: (c) => handleVisionJudge(c.env, c.req),
1936
1998
  adminJudge: (c) => handleJudge(c.env, c.req),
1937
1999
  adminJudgeAlias: (c) => handleJudge(c.env, c.req)
1938
2000
  };
@@ -4,8 +4,8 @@ import {
4
4
  requestSalt,
5
5
  resolveRequestScope,
6
6
  standardKeysFrom
7
- } from "../../chunk-PQDAL4MX.js";
8
- import "../../chunk-5UIMQAJZ.js";
7
+ } from "../../chunk-FNQ7UOEB.js";
8
+ import "../../chunk-HWWJAJWM.js";
9
9
  import "../../chunk-3FYJM7LH.js";
10
10
  export {
11
11
  entitlementScope,
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.171",
30
+ "version": "0.2.176",
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",
@@ -1007,9 +1007,47 @@ paths:
1007
1007
  context:
1008
1008
  type: string
1009
1009
  description: Surrounding clause text, at most 400 characters, to steer the description.
1010
+ model:
1011
+ type: string
1012
+ description: >-
1013
+ Per-request vision model override (an @cf/ id) — the
1014
+ caption A/B's measurement arm. Absent, the CAPTION_MODEL
1015
+ role (default glm-5.3-flash) serves.
1016
+ dry_run:
1017
+ type: boolean
1018
+ description: Return the description without storing it into the unit's payload.
1019
+ responses:
1020
+ '200':
1021
+ description: The description (stored, or returned only under dry_run).
1022
+ /admin/vision_judge:
1023
+ post:
1024
+ tags: [Administration]
1025
+ summary: Judge two captions of one figure against the image
1026
+ description: >-
1027
+ The caption A/B's pairwise judge — two anonymized descriptions of
1028
+ the same figure, presented in randomized order beside the image
1029
+ itself, decided by a third model that took no part in writing
1030
+ either. The vision A/B harness calls this lane.
1031
+ operationId: adminVisionJudge
1032
+ security: [{ adminAuth: [] }]
1033
+ requestBody:
1034
+ required: true
1035
+ content:
1036
+ application/json:
1037
+ schema:
1038
+ type: object
1039
+ required: [unit_id, captions]
1040
+ properties:
1041
+ unit_id: { type: string, description: The figure unit whose asset the captions describe. }
1042
+ captions:
1043
+ type: object
1044
+ required: [a, b]
1045
+ properties:
1046
+ a: { type: string }
1047
+ b: { type: string }
1010
1048
  responses:
1011
1049
  '200':
1012
- description: The stored description.
1050
+ description: The winning arm (a or b) with the judge's reason.
1013
1051
  /v1/admin/judge:
1014
1052
  post:
1015
1053
  tags: [Administration]
@@ -34,11 +34,16 @@ export interface AblateConfig {
34
34
  * diversified subset + one strong verifier. Off in serving until the
35
35
  * gate promotes it; the grid arms it per ask. */
36
36
  speculative: boolean;
37
- /** Extractive prompt compression (TODO.sota/02 row 5): each passage
38
- * body is query-weighted sentence selection at a fraction of its
39
- * budget before the answer model reads it. Off in serving; the grid
40
- * arms it per ask (the row's accuracy-at-token-budget gate). */
37
+ /** Extractive prompt compression (TODO.sota/02 row 5): PROMOTED — the
38
+ * passage bodies are query-weighted sentence selection at a fraction
39
+ * of their budget in serving (the row's gate: identical accuracy at
40
+ * ~1/20th the prompt, the whole-document baseline measured it).
41
+ * ablate_compress survives for the grid's explicit arm;
42
+ * ablate_no_compress is the measurement stand-down. */
41
43
  compress: boolean;
44
+ /** Per-request stand-down for the promoted compressor (the
45
+ * measurement arm; the serving default is on). */
46
+ noCompress: boolean;
42
47
  /** The decision pass (TODO.decisions/01): per-request stand-down for
43
48
  * the measurement — the serving switch is DECISIONS_ENABLED. */
44
49
  noDecisions: boolean;
@@ -64,11 +69,12 @@ export function parseAblate(
64
69
  const rawRoute = body?.ablate_route;
65
70
  const speculative = body?.ablate_speculative === true;
66
71
  const compress = body?.ablate_compress === true;
72
+ const noCompress = body?.ablate_no_compress === true;
67
73
  const noDecisions = body?.ablate_no_decisions === true;
68
74
  const namesAblation = rawStages !== undefined && rawStages !== null;
69
75
  const routeAblation = rawRoute !== undefined && rawRoute !== null && rawRoute !== "adaptive";
70
76
 
71
- if (!namesAblation && !noVerdict && !routeAblation && !speculative && !compress && !noDecisions) return { ok: true, config: null };
77
+ if (!namesAblation && !noVerdict && !routeAblation && !speculative && !compress && !noCompress && !noDecisions) return { ok: true, config: null };
72
78
 
73
79
  if (!adminToken || presentedToken !== adminToken) {
74
80
  return {
@@ -103,5 +109,5 @@ export function parseAblate(
103
109
  stages = [...new Set(rawStages as string[])];
104
110
  }
105
111
 
106
- return { ok: true, config: { stages, noVerdict, route, speculative, compress, noDecisions } };
112
+ return { ok: true, config: { stages, noVerdict, route, speculative, compress, noCompress, noDecisions } };
107
113
  }
@@ -264,6 +264,77 @@ export async function handleFamilySummary(env: Env, req: Request): Promise<Respo
264
264
  /** Ops access to the Vectorize binding (get/upsert by id) for offline
265
265
  * passes like embedding smoothing (G-ETSI-4) — the binding is the
266
266
  * credential, admin-token gated exactly like /admin/enrich. */
267
+ /** The unit's vision asset, base64-encoded for the message part — the
268
+ * one load path the caption and the pairwise judge share. */
269
+ async function loadUnitAsset(env: Env, unitId: string) {
270
+ const row = await env.DB.prepare("SELECT payload, docidentifier FROM unit_payloads WHERE unit_id = ?1").bind(unitId).first<any>();
271
+ if (!row) return { error: err(404, "not_found", "no unit_payload row for that id") };
272
+ const payload = JSON.parse(String(row.payload));
273
+ const uri = payload.uri ?? "";
274
+ const m = uri.match(/^\/assets\/(.+)/);
275
+ if (!m) return { error: err(400, "invalid_input", "payload has no /assets/ uri (upload the asset first)") };
276
+ const obj = await env.UNIT_ASSETS.get(m[1]);
277
+ if (!obj) return { error: err(404, "not_found", `asset ${m[1]} not in R2`) };
278
+ const buf = await obj.arrayBuffer();
279
+ const ext = m[1].split(".").pop()?.toLowerCase() ?? "png";
280
+ const mime = ext === "svg" ? "image/svg+xml" : `image/${ext === "jpg" ? "jpeg" : ext}`;
281
+ // chunked: spreading the whole byte array blows the V8 stack on large
282
+ // assets (the 389KB u:figure-1)
283
+ const bytes = new Uint8Array(buf);
284
+ let binary = "";
285
+ for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
286
+ return { row, b64: btoa(binary), mime };
287
+ }
288
+
289
+ /** The pairwise vision judge (TODO.sota/18.4's A/B): two anonymized
290
+ * captions of the SAME figure, presented in randomized order beside the
291
+ * image itself; a THIRD model (never a contestant) picks the better
292
+ * caption with a reason. Admin-gated; one-time quality-first lane. */
293
+ export async function handleVisionJudge(env: Env, req: Request): Promise<Response> {
294
+ if (!env.ADMIN_TOKEN) return err(501, "admin_disabled", "ADMIN_TOKEN secret is not configured");
295
+ const auth = req.headers.get("authorization") ?? "";
296
+ if (auth !== `Bearer ${env.ADMIN_TOKEN}`) return err(401, "unauthorized", "Invalid admin token");
297
+ const body = await readJson(req);
298
+ const unitId = typeof body?.unit_id === "string" ? body.unit_id : "";
299
+ const capA = typeof body?.captions?.a === "string" ? body.captions.a.slice(0, 2000) : "";
300
+ const capB = typeof body?.captions?.b === "string" ? body.captions.b.slice(0, 2000) : "";
301
+ if (!unitId || !capA || !capB) return err(400, "invalid_input", "unit_id and captions.a/.b required");
302
+ try {
303
+ const loaded = await loadUnitAsset(env, unitId);
304
+ if (loaded.error) return loaded.error;
305
+ // randomized presentation order: the judge's letter bias must not
306
+ // correlate with either arm
307
+ const swap = Math.random() < 0.5;
308
+ const first = swap ? capB : capA;
309
+ const second = swap ? capA : capB;
310
+ const res = await env.AI.run(roleModel(env, "visionJudge"), {
311
+ messages: [
312
+ {
313
+ role: "user",
314
+ content: [
315
+ { type: "text", text: `You are judging two descriptions of the same figure (from ${loaded.row.docidentifier}) written for a reader who cannot see it. The figure is attached.\n\nDescription 1: ${first}\n\nDescription 2: ${second}\n\nWhich description better tells the reader what the figure shows and its normative point? Reply with ONLY JSON: {"better": 1 or 2, "reason": "one sentence"}.` },
316
+ { type: "image_url", image_url: { url: `data:${loaded.mime};base64,${loaded.b64}` } },
317
+ ],
318
+ },
319
+ ],
320
+ // the DeepSeek law: reasoning is always on — a 2048 budget starved
321
+ // the verdict to empty content (the E.2.2.x judges failed on
322
+ // "Unexpected end of JSON input"); 4096 leaves the JSON intact
323
+ max_tokens: 4096,
324
+ });
325
+ const anyRes = res as any;
326
+ const text = typeof anyRes?.response === "string" ? anyRes.response : anyRes?.choices?.[0]?.message?.content ?? "";
327
+ const parsed = JSON.parse(text.replace(/^[^\{]*/, "").replace(/[^\}]*$/, ""));
328
+ const said = parsed?.better === 1 ? "first" : parsed?.better === 2 ? "second" : null;
329
+ if (!said) return err(502, "judge_failed", "no decision in the judge's reply: " + text.slice(0, 120));
330
+ // map back through the presentation swap
331
+ const winner = (said === "first") === swap ? "b" : "a";
332
+ return json({ ok: true, unit_id: unitId, better: winner, judge: roleModel(env, "visionJudge"), reason: String(parsed.reason ?? "").slice(0, 500) });
333
+ } catch (e) {
334
+ return err(502, "vision_judge_failed", String(e).slice(0, 200));
335
+ }
336
+ }
337
+
267
338
  /** One-time figure captioning (TODO.remaining/03): fetch the unit's asset
268
339
  * from R2, describe it with the vision-capable answer model, store the
269
340
  * description into unit_payloads. Admin-gated; idempotent. */
@@ -274,29 +345,22 @@ export async function handleCaption(env: Env, req: Request): Promise<Response> {
274
345
  const body = await readJson(req);
275
346
  const unitId = typeof body?.unit_id === "string" ? body.unit_id : "";
276
347
  const context = typeof body?.context === "string" ? body.context.slice(0, 400) : "";
348
+ // the A/B arm (TODO.sota/18.4): a per-request model override for the
349
+ // measurement, admin-gated like the route itself; dry_run skips the
350
+ // payload write so the arms never mix state in unit_payloads
351
+ const override = typeof body?.model === "string" && body.model.startsWith("@cf/") ? body.model : null;
352
+ const dryRun = body?.dry_run === true;
277
353
  if (!unitId) return err(400, "invalid_input", "unit_id required");
278
354
  try {
279
- const row = await env.DB.prepare("SELECT payload, docidentifier FROM unit_payloads WHERE unit_id = ?1").bind(unitId).first<any>();
280
- if (!row) return err(404, "not_found", "no unit_payload row for that id");
281
- const payload = JSON.parse(String(row.payload));
282
- const uri = payload.uri ?? "";
283
- const m = uri.match(/^\/assets\/(.+)/);
284
- if (!m) return err(400, "invalid_input", "payload has no /assets/ uri (upload the asset first)");
285
- const obj = await env.UNIT_ASSETS.get(m[1]);
286
- if (!obj) return err(404, "not_found", `asset ${m[1]} not in R2`);
287
- const buf = await obj.arrayBuffer();
288
- const ext = m[1].split(".").pop()?.toLowerCase() ?? "png";
289
- const mime = ext === "svg" ? "image/svg+xml" : `image/${ext === "jpg" ? "jpeg" : ext}`;
290
- // chunked: spreading the whole byte array blows the V8 stack on large
291
- // assets (the 389KB u:figure-1)
292
- const bytes = new Uint8Array(buf);
293
- let binary = "";
294
- for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
295
- const b64 = btoa(binary);
355
+ const loaded = await loadUnitAsset(env, unitId);
356
+ if (loaded.error) return loaded.error;
357
+ const row = loaded.row;
358
+ const b64 = loaded.b64;
359
+ const mime = loaded.mime;
296
360
  let res: any = null;
297
361
  for (let attempt = 0; attempt < 2 && !res; attempt++) {
298
362
  try {
299
- res = await env.AI.run(MODELS.member, {
363
+ res = await env.AI.run(override ?? roleModel(env, "caption"), {
300
364
  messages: [
301
365
  {
302
366
  role: "user",
@@ -331,8 +395,8 @@ export async function handleCaption(env: Env, req: Request): Promise<Response> {
331
395
  const end = Math.max(cut.lastIndexOf(". "), cut.lastIndexOf("! "), cut.lastIndexOf("? "));
332
396
  return end === -1 ? cut.slice(0, cut.lastIndexOf(" ")) : cut.slice(0, end + 1);
333
397
  })();
334
- await env.DB.prepare("UPDATE unit_payloads SET payload = json_set(payload, '$.description', ?1) WHERE unit_id = ?2").bind(desc, unitId).run();
335
- return json({ ok: true, unit_id: unitId, description: desc });
398
+ if (!dryRun) await env.DB.prepare("UPDATE unit_payloads SET payload = json_set(payload, '$.description', ?1) WHERE unit_id = ?2").bind(desc, unitId).run();
399
+ return json({ ok: true, unit_id: unitId, model: override ?? roleModel(env, "caption"), dry_run: dryRun || undefined, description: desc });
336
400
  } catch (e) {
337
401
  return err(502, "caption_failed", String(e).slice(0, 200));
338
402
  }
@@ -35,7 +35,7 @@ import { entitlementScope, resolveRequestScope, requestSalt, standardKeysFrom }
35
35
  import { rawSessionToken, type SessionClaims } from "./session";
36
36
  import { cacheKeyMaterial, corpusGen, exactCacheKey, freshRequested } from "./answercache";
37
37
  import { parseAblate } from "./ablate";
38
- import { decisionsEnabled, gateQuestion, runDecisionPass } from "./decisionWire";
38
+ import { decisionsEnabled, gateQuestion, runDecisionPass, telemetryDecisions } from "./decisionWire";
39
39
  import type { DecisionItem } from "./decisions";
40
40
  import { STAGE_NAMES } from "./stages/index.ts";
41
41
  import { routeFor, fastRouteStages } from "./route.ts";
@@ -1507,7 +1507,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1507
1507
  [ops.note, processNote, eNote, regNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, aggregationNote, boundaryNote, licenseNote, ...(retrieved.notes ?? [])].filter(Boolean).join("\n") || undefined,
1508
1508
  summary,
1509
1509
  budget,
1510
- ablate?.compress === true,
1510
+ ablate?.noCompress !== true,
1511
1511
  );
1512
1512
  // the decision pass (TODO.decisions/01): starts here so its calls run
1513
1513
  // concurrently with generation — the checklist section arrives the
@@ -1730,15 +1730,21 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1730
1730
  let streamedCompletionBlocks: Awaited<ReturnType<typeof completeTables>> = [];
1731
1731
  // the decision pass's section streams as the answer's tail
1732
1732
  // (the pass ran concurrently with generation — its arrival is
1733
- // instant here), before the completions and the done event
1734
- if (decisionPromise && !canonical0.includes(refusalAnswer())) {
1733
+ // instant here), before the completions and the done event.
1734
+ // Stand-down law (TODO.decisions/00): where a deterministic
1735
+ // engine (verdict, condition, aggregation) answered, its block
1736
+ // is authoritative — the checklist never rides beside it.
1737
+ let streamedDecisions: { mode: string; gate: number; items: DecisionItem[] } | undefined;
1738
+ if (decisionPromise && !canonical0.includes(refusalAnswer()) && !verdictBlock && !conditionBlock && !aggregationBlock) {
1735
1739
  const dp = await decisionPromise;
1736
1740
  if (dp) {
1737
1741
  canonical0 += dp.section;
1738
1742
  send({ type: "token", v: dp.section });
1739
1743
  full += dp.section;
1744
+ streamedDecisions = { mode: dp.mode, gate: dp.gate, items: dp.items };
1740
1745
  console.log("decisions: streamed the checklist section (" + dp.items.length + " items, mode " + dp.mode + ")");
1741
1746
  }
1747
+ telemetryDecisions(env, ctx, tier, queryHash, streamedDecisions ?? null, Date.now() - tStart);
1742
1748
  }
1743
1749
  if (!canonical0.includes(refusalAnswer())) {
1744
1750
  try {
@@ -1747,7 +1753,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1747
1753
  if (streamedCompletionBlocks.length) console.log("streamed completion:", streamedCompletionBlocks.length, "block(s) attached");
1748
1754
  } catch { /* additive — the answer stands without the blocks */ }
1749
1755
  }
1750
- send({ type: "done", model, query_hash: queryHash, follow_ups: refusalShaped(canonical0, refusalScope(answerCites(full))) ? [] : (understanding?.follow_ups ?? []), blocks: [...c2.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : []), ...streamedCompletionBlocks], context_applied: ctxApplied, read: readAs(),
1756
+ send({ type: "done", model, query_hash: queryHash, follow_ups: refusalShaped(canonical0, refusalScope(answerCites(full))) ? [] : (understanding?.follow_ups ?? []), ...(streamedDecisions ? { decisions: streamedDecisions } : {}), blocks: [...c2.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : []), ...streamedCompletionBlocks], context_applied: ctxApplied, read: readAs(),
1751
1757
  // the evidence view's ground truth: the exact passages this
1752
1758
  // answer was built from, compact — cache hits carry none,
1753
1759
  // because the cache stores the answer and never the passages
@@ -1999,7 +2005,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1999
2005
  ...(routeSubset ? { stageSubset: routeSubset ?? undefined } : {}),
2000
2006
  });
2001
2007
  if (retryRetrieve.hits.length > 0) {
2002
- const { messages: retryMessages, usedHits: retryUsed } = buildMessages(q.query, retryRetrieve.hits, q.lang, keptHistory, undefined, summary, budget, ablate?.compress === true);
2008
+ const { messages: retryMessages, usedHits: retryUsed } = buildMessages(q.query, retryRetrieve.hits, q.lang, keptHistory, undefined, summary, budget, ablate?.noCompress !== true);
2003
2009
  const retryAnswer = await generateOnce(env, model, retryMessages, routeEffort(routed.route));
2004
2010
  // the answer now comes from the retry passages — citations must follow
2005
2011
  if (retryAnswer) {
@@ -2083,10 +2089,11 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
2083
2089
  let decisionsOut: { mode: string; gate: number; items: DecisionItem[] } | undefined;
2084
2090
  if (decisionPromise) {
2085
2091
  const dp = await decisionPromise;
2086
- if (dp && answer && !answer!.includes(refusalAnswer())) {
2092
+ if (dp && answer && !answer!.includes(refusalAnswer()) && !verdictBlock && !conditionBlock && !aggregationBlock) {
2087
2093
  answer = answer! + dp.section;
2088
2094
  decisionsOut = { mode: dp.mode, gate: dp.gate, items: dp.items };
2089
2095
  }
2096
+ telemetryDecisions(env, ctx, tier, queryHash, decisionsOut ?? null, Date.now() - tStart);
2090
2097
  }
2091
2098
  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: refusalShaped(answer!, refusalScope(answerCites(answer!))) ? [] : (understanding?.follow_ups ?? []), blocks: [...c2ns.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : []), ...(registerBlock ? [registerBlock] : []), ...completionBlocks], context_applied: ctxApplied, ...(liveRecords ? { records: liveRecords } : {}), ...(decisionsOut ? { decisions: decisionsOut } : {}), ...(ablate ? { ablate } : {}), route: { route: routed.route, features: routed.features } };
2092
2099
  const cacheable = !contextual && !declaredCtx && !ablate && cacheableAnswer(answer!, refusalScope(answerCites(answer!))) && finalAnchors.violations.length === 0;
@@ -7,6 +7,13 @@ export const MODELS = {
7
7
  // Vision image-parts land with answer contract v2; text answers work now.
8
8
  anon: "@cf/zai-org/glm-5.3-flash",
9
9
  member: "@cf/zai-org/glm-5.3-flash",
10
+ // figure captions + the vision lane (one-time, quality-first; the A/B
11
+ // lever is CAPTION_MODEL, the per-request arm is the admin route's
12
+ // body.model)
13
+ caption: "@cf/zai-org/glm-5.3-flash",
14
+ // the pairwise vision judge (sees the image; a THIRD model never
15
+ // judges its own arm)
16
+ visionJudge: "@cf/deepseek-ai/deepseek-v4.1-flash-1017",
10
17
  // the VLM figure rerank (TODO.sota/18.3): the newly-landed
11
18
  // vision-capable MoE scores figure IMAGES against figure-seeking
12
19
  // questions — one bounded call per figure question, ablatable stage
@@ -4,6 +4,7 @@
4
4
  // when DECISIONS_ENABLED, not ablated, not a refusal, no verdict block
5
5
  // bound, and the gate's noul clears decisionsGateMin.
6
6
  import { portModelRunner } from "./env.ts";
7
+ import { telemetry } from "./quota";
7
8
  import { MODELS, THRESHOLDS } from "./config.ts";
8
9
  import {
9
10
  buildDecisionQuestions, matchAnswers, parseDerivation,
@@ -126,3 +127,27 @@ export async function runDecisionPass(
126
127
  return null; // additive — the essay answer ships
127
128
  }
128
129
  }
130
+
131
+ /** The decisions telemetry (TODO.decisions/03): the nightly's
132
+ * calibration signal, riding the queries table's route key with a
133
+ * defined encoding — no schema migration.
134
+ * - one PASS row, route "ask-decisions": ok = fired, lang = mode,
135
+ * model = the gate probability (2dp), duration_ms = the pass's.
136
+ * - one ITEM row per requirement, route "ask-decision": ok = band is
137
+ * "met", lang = band, model = the probability (2dp); query_hash
138
+ * joins the rows to their pass. */
139
+ export function telemetryDecisions(
140
+ env: Env,
141
+ ctx: { waitUntil(p: Promise<unknown>): void },
142
+ tier: string,
143
+ queryHash: string,
144
+ result: { mode: string; gate: number; items: DecisionItem[] } | null,
145
+ durationMs: number,
146
+ ): void {
147
+ try {
148
+ telemetry(env, ctx, tier, "ask-decisions", result ? result.gate.toFixed(2) : "0", !!result, result?.items.length ?? 0, queryHash, result?.mode ?? undefined, undefined, { durationMs });
149
+ for (const it of result?.items ?? []) {
150
+ telemetry(env, ctx, tier, "ask-decision", it.probability.toFixed(2), it.band === "met", 1, queryHash, it.band, undefined, {});
151
+ }
152
+ } catch { /* telemetry never answers for the answer */ }
153
+ }
@@ -23,7 +23,7 @@ import { json, err, corsHeaders, withCors, readJson, authenticate, type ApiKey }
23
23
  import { creditBudget, clientIp } from "./quota";
24
24
 
25
25
  import { handleSearch } from "./search";
26
- import { handleEnrich, handleSectionUnit, handleCaption, handleVectors, handleJudge, handleCreateKey, handleListKeys, handleRevokeKey, handleKeyUsage, handleFamilySummary } from "./admin";
26
+ import { handleEnrich, handleSectionUnit, handleCaption, handleVisionJudge, handleVectors, handleJudge, handleCreateKey, handleListKeys, handleRevokeKey, handleKeyUsage, handleFamilySummary } from "./admin";
27
27
  import { handleResearch } from "./research";
28
28
  import { uploadAttachment, readAttachment, deleteAttachment, type AttachmentDeps } from "./attachments";
29
29
  import { cfBlobs } from "./ports/cloudflare/adapters";
@@ -639,6 +639,7 @@ const OPENAPI_HANDLERS: Record<OpenApiOperationId, RouteHandler> = {
639
639
  adminSectionAlias: (c) => handleSectionUnit(c.env, c.ctx, c.req),
640
640
  adminVectors: (c) => handleVectors(c.env, c.req),
641
641
  adminCaption: (c) => handleCaption(c.env, c.req),
642
+ adminVisionJudge: (c) => handleVisionJudge(c.env, c.req),
642
643
  adminJudge: (c) => handleJudge(c.env, c.req),
643
644
  adminJudgeAlias: (c) => handleJudge(c.env, c.req),
644
645
  };
@@ -53,6 +53,7 @@ export type OpenApiOperationId =
53
53
  | "adminFamilySummaryAlias"
54
54
  | "adminVectors"
55
55
  | "adminCaption"
56
+ | "adminVisionJudge"
56
57
  | "adminJudge"
57
58
  | "adminJudgeAlias"
58
59
  | "adminRevokeKey"
@@ -296,6 +297,11 @@ export const OPENAPI_SURFACE: readonly (Omit<OpenApiRoute, "operationId"> & { op
296
297
  "pattern": "/admin/caption",
297
298
  "operationId": "adminCaption"
298
299
  },
300
+ {
301
+ "method": "POST",
302
+ "pattern": "/admin/vision_judge",
303
+ "operationId": "adminVisionJudge"
304
+ },
299
305
  {
300
306
  "method": "POST",
301
307
  "pattern": "/v1/admin/judge",
@@ -288,9 +288,10 @@ export function buildMessages(
288
288
  retrievalNote?: string,
289
289
  conversationSummary?: string,
290
290
  budgetTokens: number = LIMITS.inputTokenBudget,
291
- /** the extractive compression arm (TODO.sota/02 row 5): ablate_compress
292
- * arms it per ask — each passage body becomes query-weighted sentence
293
- * selection at compressShare of its budget. Off in serving. */
291
+ /** the extractive compression (TODO.sota/02 row 5): PROMOTED — on in
292
+ * serving; ablate_no_compress stands it down per ask for the
293
+ * measurement. Each passage body becomes query-weighted sentence
294
+ * selection at compressShare of its budget. */
294
295
  compress?: boolean,
295
296
  ): BuiltMessages {
296
297
  // per-corpus guidance travels WITH the dataset (config.ts): every
@@ -1,16 +0,0 @@
1
- import {
2
- handleAsk
3
- } from "./chunk-KKJHJXQV.js";
4
- import "./chunk-ECOYO7EX.js";
5
- import "./chunk-QS6V53RO.js";
6
- import "./chunk-RZLEHCIA.js";
7
- import "./chunk-XWSZWFYP.js";
8
- import "./chunk-O2XMNKCK.js";
9
- import "./chunk-ETLSWSZ7.js";
10
- import "./chunk-PQDAL4MX.js";
11
- import "./chunk-5UIMQAJZ.js";
12
- import "./chunk-3FYJM7LH.js";
13
- import "./chunk-KINFFYQH.js";
14
- export {
15
- handleAsk
16
- };
@@ -1,14 +0,0 @@
1
- import {
2
- handleSearch
3
- } from "./chunk-6KEWBP7O.js";
4
- import "./chunk-RZLEHCIA.js";
5
- import "./chunk-XWSZWFYP.js";
6
- import "./chunk-O2XMNKCK.js";
7
- import "./chunk-ETLSWSZ7.js";
8
- import "./chunk-PQDAL4MX.js";
9
- import "./chunk-5UIMQAJZ.js";
10
- import "./chunk-3FYJM7LH.js";
11
- import "./chunk-KINFFYQH.js";
12
- export {
13
- handleSearch
14
- };