@konneal/engine 0.1.0

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 (285) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +13 -0
  3. package/dist/admin.d.ts +26 -0
  4. package/dist/ai.d.ts +6 -0
  5. package/dist/anchors.d.ts +6 -0
  6. package/dist/answercache.d.ts +22 -0
  7. package/dist/ask.d.ts +5 -0
  8. package/dist/auth.d.ts +12 -0
  9. package/dist/bubble.d.ts +14 -0
  10. package/dist/chunk-LLWPT2XV.js +49 -0
  11. package/dist/chunk-MB74PTRM.js +114 -0
  12. package/dist/chunk-WOGQM7DJ.js +197 -0
  13. package/dist/chunk-WWNCWKKC.js +42 -0
  14. package/dist/completion.d.ts +5 -0
  15. package/dist/config.d.ts +154 -0
  16. package/dist/config.js +37 -0
  17. package/dist/context.d.ts +115 -0
  18. package/dist/conversations.d.ts +5 -0
  19. package/dist/drafts.d.ts +129 -0
  20. package/dist/env.d.ts +57 -0
  21. package/dist/faithfulness.d.ts +5 -0
  22. package/dist/grader.d.ts +3 -0
  23. package/dist/graph.d.ts +13 -0
  24. package/dist/hybrid.d.ts +7 -0
  25. package/dist/index.d.ts +9 -0
  26. package/dist/index.js +5373 -0
  27. package/dist/internal_gateway.d.ts +14 -0
  28. package/dist/lexical.d.ts +7 -0
  29. package/dist/livedata.d.ts +77 -0
  30. package/dist/memories.d.ts +10 -0
  31. package/dist/modelplane.d.ts +61 -0
  32. package/dist/oidc.d.ts +73 -0
  33. package/dist/pipeline.d.ts +57 -0
  34. package/dist/profile.d.ts +2 -0
  35. package/dist/profile.gen.d.ts +70 -0
  36. package/dist/profile.js +8 -0
  37. package/dist/projects.d.ts +8 -0
  38. package/dist/prompts/conversational.md +8 -0
  39. package/dist/prompts/enrichment.md +3 -0
  40. package/dist/prompts/faithfulness.md +1 -0
  41. package/dist/prompts/grader.md +5 -0
  42. package/dist/prompts/listwise.md +3 -0
  43. package/dist/prompts/precision.md +1 -0
  44. package/dist/prompts/reflect.md +1 -0
  45. package/dist/prompts/relevancy.md +1 -0
  46. package/dist/prompts/research.md +10 -0
  47. package/dist/prompts/section-summary.md +5 -0
  48. package/dist/prompts/summarize.md +1 -0
  49. package/dist/prompts/system.md +18 -0
  50. package/dist/prompts/understanding.md +17 -0
  51. package/dist/quota.d.ts +13 -0
  52. package/dist/reflect.d.ts +5 -0
  53. package/dist/refs.d.ts +40 -0
  54. package/dist/refusal.d.ts +9 -0
  55. package/dist/refusal.js +9 -0
  56. package/dist/requestScope.d.ts +26 -0
  57. package/dist/requestScope.js +10 -0
  58. package/dist/research.d.ts +8 -0
  59. package/dist/search.d.ts +4 -0
  60. package/dist/selfquery.d.ts +7 -0
  61. package/dist/session.d.ts +1 -0
  62. package/dist/share.d.ts +2 -0
  63. package/dist/structural.d.ts +27 -0
  64. package/dist/tablecontext.d.ts +11 -0
  65. package/dist/understand.d.ts +11 -0
  66. package/dist/understandContract.d.ts +29 -0
  67. package/dist/verdict.d.ts +24 -0
  68. package/docs/API.md +451 -0
  69. package/docs/ARCHITECTURE.md +302 -0
  70. package/docs/AUDIT-2026-08-24.md +71 -0
  71. package/docs/CONTRIBUTOR-AUDIT-2026-08-25.md +147 -0
  72. package/docs/INGEST-ARCHITECTURE.md +158 -0
  73. package/docs/MCP.md +92 -0
  74. package/docs/METANORMA-AI-SERIALIZATION.md +247 -0
  75. package/docs/MKO-EXPORT-PIPELINE.md +147 -0
  76. package/docs/REDESIGN-NORMATIVE-RAG-ETSI.md +485 -0
  77. package/docs/RESEARCH-SOTA-2026.md +243 -0
  78. package/docs/ROADMAP-SOTA.md +130 -0
  79. package/docs/SOTA-STAGE-SPECS.md +509 -0
  80. package/docs/annealment/F1-verdict.md +27 -0
  81. package/docs/annealment/F10-notes.md +19 -0
  82. package/docs/annealment/F11-composition.md +17 -0
  83. package/docs/annealment/F12-passport.md +17 -0
  84. package/docs/annealment/F2-counterfactual.md +20 -0
  85. package/docs/annealment/F3-absence.md +21 -0
  86. package/docs/annealment/F4-instance.md +18 -0
  87. package/docs/annealment/F5-workflow.md +21 -0
  88. package/docs/annealment/F6-impact.md +21 -0
  89. package/docs/annealment/F7-editions.md +18 -0
  90. package/docs/annealment/F8-selfverify.md +19 -0
  91. package/docs/annealment/F9-projection-qa.md +17 -0
  92. package/docs/annealment/L0-locate.md +19 -0
  93. package/docs/annealment/L1-extract.md +18 -0
  94. package/docs/annealment/L2-nomenclature.md +22 -0
  95. package/docs/annealment/L3-geometry.md +23 -0
  96. package/docs/annealment/L4-composition.md +21 -0
  97. package/docs/annealment/L5-cross-standard.md +20 -0
  98. package/docs/annealment/L6-diachrony.md +21 -0
  99. package/docs/annealment/L7-perception.md +20 -0
  100. package/docs/annealment/L8-computation.md +22 -0
  101. package/docs/annealment/L9-instance-process.md +23 -0
  102. package/docs/annealment/README.md +10 -0
  103. package/docs/guidelines-metanorma-ai-programme.md +279 -0
  104. package/docs/identity-onboarding-rag.md +65 -0
  105. package/docs/identity-service.md +219 -0
  106. package/docs/knowledge-annealment.md +273 -0
  107. package/docs/konneal-extraction-plan.md +481 -0
  108. package/docs/metanorma-for-ai.md +270 -0
  109. package/docs/mirror-plan.md +36 -0
  110. package/docs/multi-sdo-architecture.md +191 -0
  111. package/docs/paper-annealment-comparison.md +259 -0
  112. package/docs/paper-assets/architecture.svg +94 -0
  113. package/docs/paper-assets/contract-v2.svg +94 -0
  114. package/docs/paper-assets/mko-ingest.svg +91 -0
  115. package/docs/paper-oiml-bulletin.md +402 -0
  116. package/docs/paper-oiml-bulletin.mdx +419 -0
  117. package/docs/product-branding-options.md +172 -0
  118. package/docs/projects-design.md +88 -0
  119. package/docs/sota-mechanisms.md +184 -0
  120. package/docs/spec-api.md +77 -0
  121. package/docs/spec-pipeline.md +126 -0
  122. package/docs/vector-adapter.md +88 -0
  123. package/package.json +70 -0
  124. package/profile/corpora.yaml +5 -0
  125. package/profile/datasets.yaml +14 -0
  126. package/profile/prompts.yaml +5 -0
  127. package/profile/publisher.yaml +17 -0
  128. package/profile/retrieval.yaml +1 -0
  129. package/profile/sources.yaml +5 -0
  130. package/profile/ui.yaml +7 -0
  131. package/scripts/gen_profile.mjs +33 -0
  132. package/workers/shared/ai.ts +21 -0
  133. package/workers/shared/auth.ts +16 -0
  134. package/workers/shared/chunk.ts +108 -0
  135. package/workers/shared/oidc.ts +312 -0
  136. package/workers/shared/router.ts +45 -0
  137. package/workers/shared/session.ts +104 -0
  138. package/workers/worker_internal/src/index.ts +157 -0
  139. package/workers/worker_internal/tsconfig.json +15 -0
  140. package/workers/worker_internal/wrangler.toml +32 -0
  141. package/workers/worker_mcp/src/index.ts +175 -0
  142. package/workers/worker_mcp/tsconfig.json +13 -0
  143. package/workers/worker_mcp/wrangler.toml +18 -0
  144. package/workers/worker_public/migrations/0002_conversations.sql +22 -0
  145. package/workers/worker_public/migrations/0003_shared_conversations.sql +9 -0
  146. package/workers/worker_public/migrations/0004_graph.sql +16 -0
  147. package/workers/worker_public/migrations/0005_documents.sql +19 -0
  148. package/workers/worker_public/migrations/0006_conversation_entities.sql +11 -0
  149. package/workers/worker_public/migrations/0007_chunks_fts.sql +43 -0
  150. package/workers/worker_public/migrations/0008_unit_payloads.sql +15 -0
  151. package/workers/worker_public/migrations/0009_chunks_unit.sql +8 -0
  152. package/workers/worker_public/migrations/0009_message_context.sql +7 -0
  153. package/workers/worker_public/migrations/0010_model_nodes.sql +33 -0
  154. package/workers/worker_public/migrations/0011_schema_union.sql +38 -0
  155. package/workers/worker_public/migrations/0012_memories.sql +15 -0
  156. package/workers/worker_public/migrations/0013_projects.sql +21 -0
  157. package/workers/worker_public/node_modules/@cloudflare/workers-types/2021-11-03/index.d.ts +16306 -0
  158. package/workers/worker_public/node_modules/@cloudflare/workers-types/2021-11-03/index.ts +16261 -0
  159. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-01-31/index.d.ts +16373 -0
  160. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-01-31/index.ts +16328 -0
  161. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-03-21/index.d.ts +16382 -0
  162. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-03-21/index.ts +16337 -0
  163. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-08-04/index.d.ts +16383 -0
  164. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-08-04/index.ts +16338 -0
  165. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-10-31/index.d.ts +16403 -0
  166. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-10-31/index.ts +16358 -0
  167. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-11-30/index.d.ts +16408 -0
  168. package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-11-30/index.ts +16363 -0
  169. package/workers/worker_public/node_modules/@cloudflare/workers-types/2023-03-01/index.d.ts +16414 -0
  170. package/workers/worker_public/node_modules/@cloudflare/workers-types/2023-03-01/index.ts +16369 -0
  171. package/workers/worker_public/node_modules/@cloudflare/workers-types/2023-07-01/index.d.ts +16414 -0
  172. package/workers/worker_public/node_modules/@cloudflare/workers-types/2023-07-01/index.ts +16369 -0
  173. package/workers/worker_public/node_modules/@cloudflare/workers-types/README.md +135 -0
  174. package/workers/worker_public/node_modules/@cloudflare/workers-types/entrypoints.svg +53 -0
  175. package/workers/worker_public/node_modules/@cloudflare/workers-types/experimental/index.d.ts +17095 -0
  176. package/workers/worker_public/node_modules/@cloudflare/workers-types/experimental/index.ts +17050 -0
  177. package/workers/worker_public/node_modules/@cloudflare/workers-types/index.d.ts +16306 -0
  178. package/workers/worker_public/node_modules/@cloudflare/workers-types/index.ts +16261 -0
  179. package/workers/worker_public/node_modules/@cloudflare/workers-types/latest/index.d.ts +16447 -0
  180. package/workers/worker_public/node_modules/@cloudflare/workers-types/latest/index.ts +16402 -0
  181. package/workers/worker_public/node_modules/@cloudflare/workers-types/oldest/index.d.ts +16306 -0
  182. package/workers/worker_public/node_modules/@cloudflare/workers-types/oldest/index.ts +16261 -0
  183. package/workers/worker_public/node_modules/@cloudflare/workers-types/package.json +11 -0
  184. package/workers/worker_public/package.json +13 -0
  185. package/workers/worker_public/prompts/conversational.md +8 -0
  186. package/workers/worker_public/prompts/enrichment.md +3 -0
  187. package/workers/worker_public/prompts/faithfulness.md +1 -0
  188. package/workers/worker_public/prompts/grader.md +5 -0
  189. package/workers/worker_public/prompts/listwise.md +3 -0
  190. package/workers/worker_public/prompts/precision.md +1 -0
  191. package/workers/worker_public/prompts/reflect.md +1 -0
  192. package/workers/worker_public/prompts/relevancy.md +1 -0
  193. package/workers/worker_public/prompts/research.md +10 -0
  194. package/workers/worker_public/prompts/section-summary.md +5 -0
  195. package/workers/worker_public/prompts/summarize.md +1 -0
  196. package/workers/worker_public/prompts/system.md +18 -0
  197. package/workers/worker_public/prompts/understanding.md +17 -0
  198. package/workers/worker_public/public/app.js +166 -0
  199. package/workers/worker_public/public/index.html +48 -0
  200. package/workers/worker_public/public/style.css +147 -0
  201. package/workers/worker_public/schema.sql +248 -0
  202. package/workers/worker_public/src/admin.ts +358 -0
  203. package/workers/worker_public/src/ai.ts +71 -0
  204. package/workers/worker_public/src/anchors.ts +41 -0
  205. package/workers/worker_public/src/answercache.ts +72 -0
  206. package/workers/worker_public/src/ask.ts +1094 -0
  207. package/workers/worker_public/src/auth.ts +252 -0
  208. package/workers/worker_public/src/bubble.ts +111 -0
  209. package/workers/worker_public/src/completion.ts +75 -0
  210. package/workers/worker_public/src/config.ts +238 -0
  211. package/workers/worker_public/src/context.ts +238 -0
  212. package/workers/worker_public/src/conversations.ts +162 -0
  213. package/workers/worker_public/src/drafts.ts +497 -0
  214. package/workers/worker_public/src/env.ts +90 -0
  215. package/workers/worker_public/src/faithfulness.ts +63 -0
  216. package/workers/worker_public/src/grader.ts +89 -0
  217. package/workers/worker_public/src/graph.ts +63 -0
  218. package/workers/worker_public/src/hybrid.ts +77 -0
  219. package/workers/worker_public/src/index.ts +441 -0
  220. package/workers/worker_public/src/internal_gateway.ts +41 -0
  221. package/workers/worker_public/src/lexical.ts +86 -0
  222. package/workers/worker_public/src/lib/hit.ts +4 -0
  223. package/workers/worker_public/src/lib/http.ts +83 -0
  224. package/workers/worker_public/src/lib/router.ts +4 -0
  225. package/workers/worker_public/src/livedata.ts +334 -0
  226. package/workers/worker_public/src/memories.ts +81 -0
  227. package/workers/worker_public/src/modelplane.ts +213 -0
  228. package/workers/worker_public/src/oidc.ts +333 -0
  229. package/workers/worker_public/src/pipeline.ts +377 -0
  230. package/workers/worker_public/src/ports/blobs.ts +7 -0
  231. package/workers/worker_public/src/ports/cloudflare/adapters.ts +177 -0
  232. package/workers/worker_public/src/ports/kv.ts +8 -0
  233. package/workers/worker_public/src/ports/model.ts +28 -0
  234. package/workers/worker_public/src/ports/runtime.ts +13 -0
  235. package/workers/worker_public/src/ports/store.ts +20 -0
  236. package/workers/worker_public/src/ports/vector.ts +26 -0
  237. package/workers/worker_public/src/profile.gen.ts +101 -0
  238. package/workers/worker_public/src/profile.ts +16 -0
  239. package/workers/worker_public/src/projects.ts +108 -0
  240. package/workers/worker_public/src/prompts.d.ts +6 -0
  241. package/workers/worker_public/src/quota.ts +54 -0
  242. package/workers/worker_public/src/reflect.ts +67 -0
  243. package/workers/worker_public/src/refs.ts +107 -0
  244. package/workers/worker_public/src/refusal.ts +65 -0
  245. package/workers/worker_public/src/requestScope.ts +71 -0
  246. package/workers/worker_public/src/research.ts +126 -0
  247. package/workers/worker_public/src/search.ts +56 -0
  248. package/workers/worker_public/src/selfquery.ts +25 -0
  249. package/workers/worker_public/src/session.ts +4 -0
  250. package/workers/worker_public/src/share.ts +53 -0
  251. package/workers/worker_public/src/stages/conceptGraph.ts +68 -0
  252. package/workers/worker_public/src/stages/conceptSteer.ts +39 -0
  253. package/workers/worker_public/src/stages/corpusScope.ts +25 -0
  254. package/workers/worker_public/src/stages/dedup.ts +10 -0
  255. package/workers/worker_public/src/stages/dense.ts +73 -0
  256. package/workers/worker_public/src/stages/diversity.ts +33 -0
  257. package/workers/worker_public/src/stages/editionCover.ts +63 -0
  258. package/workers/worker_public/src/stages/editionSteer.ts +88 -0
  259. package/workers/worker_public/src/stages/familyBoost.ts +22 -0
  260. package/workers/worker_public/src/stages/federate.ts +22 -0
  261. package/workers/worker_public/src/stages/glossary.ts +65 -0
  262. package/workers/worker_public/src/stages/graphLane.ts +31 -0
  263. package/workers/worker_public/src/stages/hyde.ts +29 -0
  264. package/workers/worker_public/src/stages/index.ts +69 -0
  265. package/workers/worker_public/src/stages/lexicalUnion.ts +21 -0
  266. package/workers/worker_public/src/stages/multiQuery.ts +57 -0
  267. package/workers/worker_public/src/stages/overviewDemote.ts +14 -0
  268. package/workers/worker_public/src/stages/poolOpen.ts +10 -0
  269. package/workers/worker_public/src/stages/propagate.ts +15 -0
  270. package/workers/worker_public/src/stages/rerank.ts +47 -0
  271. package/workers/worker_public/src/stages/seal.ts +16 -0
  272. package/workers/worker_public/src/stages/sectionDescent.ts +61 -0
  273. package/workers/worker_public/src/stages/stdRefNudge.ts +35 -0
  274. package/workers/worker_public/src/stages/subQuery.ts +42 -0
  275. package/workers/worker_public/src/stages/termNudge.ts +24 -0
  276. package/workers/worker_public/src/stages/typedPin.ts +131 -0
  277. package/workers/worker_public/src/stages/types.ts +112 -0
  278. package/workers/worker_public/src/stages/windowFloor.ts +23 -0
  279. package/workers/worker_public/src/structural.ts +171 -0
  280. package/workers/worker_public/src/tablecontext.ts +41 -0
  281. package/workers/worker_public/src/understand.ts +72 -0
  282. package/workers/worker_public/src/understandContract.ts +67 -0
  283. package/workers/worker_public/src/verdict.ts +255 -0
  284. package/workers/worker_public/tsconfig.json +18 -0
  285. package/workers/worker_public/wrangler.toml +104 -0
@@ -0,0 +1,157 @@
1
+ // worker_internal: federated OIML + ISO/IEC retrieval provider for members.
2
+ // Structural isolation per CLAUDE.md — the PUBLIC worker holds no binding
3
+ // to the internal index; this worker holds both and gates by session.
4
+ // Retrieval-only by design (SSOT): the full serving pipeline — query
5
+ // understanding, fusion, reranking, grading, generation — lives in
6
+ // worker_public; this worker answers /retrieve with ranked passages.
7
+
8
+ import { sessionFrom } from "../../shared/auth";
9
+ import { embed } from "../../shared/ai";
10
+ import { toHits } from "../../shared/chunk";
11
+ import { matchRoute, type RouteContext, type Route } from "../../shared/router";
12
+ import type { Hit } from "../../shared/chunk";
13
+
14
+ export type { ChunkMeta, Hit };
15
+
16
+ export interface Env {
17
+ AI: any;
18
+ PUBLIC: any; // idx_oiml_public_v2
19
+ INTERNAL: any; // idx_iso_internal
20
+ CACHE: KVNamespace;
21
+ SESSION_SECRET: string;
22
+ INDEX_VERSION: string;
23
+ ADMIN_TOKEN?: string;
24
+ }
25
+
26
+ const RRF_K = 60;
27
+
28
+ const json = (body: unknown, status = 200) =>
29
+ new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json", "access-control-allow-origin": "*" } });
30
+
31
+ const err = (status: number, code: string, message: string) =>
32
+ json({ error: { code, message } }, status);
33
+
34
+ /** Query both indexes and RRF-fuse the rankings; internal (ISO/IEC) hits
35
+ * carry a slight weight so members see them when both corpora match. */
36
+ async function federate(env: Env, query: string, topK = 20): Promise<Hit[]> {
37
+ const vector = await embed(env.AI, query);
38
+ const [pubRes, intRes] = await Promise.all([
39
+ env.PUBLIC.query(vector, { topK, returnMetadata: "all" }),
40
+ env.INTERNAL.query(vector, { topK: Math.min(topK, 10), returnMetadata: "all" }),
41
+ ]);
42
+ const scores = new Map<string, number>();
43
+ const byId = new Map<string, Hit>();
44
+ // weighted RRF: the internal corpus outranks the public corpus on
45
+ // tie (federation emphasis — deliberately NOT the shared unweighted
46
+ // fuse; this worker's only job is the merged internal-first ranking)
47
+ const rankings: [Hit[], number][] = [
48
+ [toHits(pubRes.matches ?? []), 1.0],
49
+ [toHits(intRes.matches ?? []), 1.2],
50
+ ];
51
+ for (const [ranking, weight] of rankings) {
52
+ ranking.forEach((h, i) => {
53
+ const s = weight / (RRF_K + i + 1);
54
+ scores.set(h.id, (scores.get(h.id) ?? 0) + s);
55
+ byId.set(h.id, h);
56
+ });
57
+ }
58
+ return [...scores.entries()]
59
+ .sort((a, b) => b[1] - a[1])
60
+ .slice(0, 15)
61
+ .map(([id]) => byId.get(id)!)
62
+ .filter(Boolean);
63
+ }
64
+
65
+ async function healthRoute(c: RouteContext): Promise<Response> {
66
+ return json({ ok: true, service: "rag-internal", index_version: c.env.INDEX_VERSION });
67
+ }
68
+
69
+ // Index sync through the Vectorize/AI bindings — used by the ingest
70
+ // pipeline when no REST API token is provisioned. Guarded by the
71
+ // ADMIN_TOKEN secret; batches stay small enough for one request.
72
+ async function adminSyncRoute(c: RouteContext): Promise<Response> {
73
+ const { env, req } = c;
74
+ if (!env.ADMIN_TOKEN || req.headers.get("x-admin-token") !== env.ADMIN_TOKEN) {
75
+ return err(401, "unauthorized", "admin token required");
76
+ }
77
+ let body: any;
78
+ try {
79
+ body = await req.json();
80
+ } catch {
81
+ return err(400, "invalid_input", "JSON body required");
82
+ }
83
+ const out = { upserted: 0, deleted: 0, embedded: 0 };
84
+ try {
85
+ if (Array.isArray(body?.upserts)) {
86
+ for (let i = 0; i < body.upserts.length; i += 100) {
87
+ const batch = body.upserts.slice(i, i + 100).filter((v: any) => v?.id && Array.isArray(v?.values) && v?.metadata);
88
+ if (batch.length) await env.PUBLIC.upsert(batch);
89
+ out.upserted += batch.length;
90
+ }
91
+ }
92
+ if (Array.isArray(body?.embedUpserts)) {
93
+ const todo = body.embedUpserts.filter((v: any) => v?.id && typeof v?.text === "string" && v?.metadata);
94
+ for (let i = 0; i < todo.length; i += 16) {
95
+ const batch = todo.slice(i, i + 16);
96
+ const vectors = await Promise.all(batch.map((b: any) => embed(env.AI, b.text.slice(0, 6000))));
97
+ await env.PUBLIC.upsert(batch.map((b: any, j: number) => ({ id: b.id, values: vectors[j], metadata: b.metadata })));
98
+ out.embedded += batch.length;
99
+ }
100
+ }
101
+ if (Array.isArray(body?.deletes)) {
102
+ const ids = body.deletes.filter((x: any) => typeof x === "string");
103
+ for (let i = 0; i < ids.length; i += 100) {
104
+ await env.PUBLIC.deleteByIds(ids.slice(i, i + 100));
105
+ out.deleted += Math.min(100, ids.length - i);
106
+ }
107
+ }
108
+ return json(out);
109
+ } catch (e: any) {
110
+ return err(502, "sync_failed", e?.message ?? "vectorize operation failed");
111
+ }
112
+ }
113
+
114
+ async function retrieveRoute(c: RouteContext): Promise<Response> {
115
+ const session = await sessionFrom(c.req, c.env as any);
116
+ if (!session) return err(401, "unauthorized", "Sign in required — this endpoint federates the OIML + ISO/IEC corpora.");
117
+ let body: any;
118
+ try {
119
+ body = await c.req.json();
120
+ } catch {
121
+ return err(400, "invalid_input", "JSON body required");
122
+ }
123
+ const query = typeof body?.query === "string" ? body.query.trim() : "";
124
+ if (!query || query.length > 1200) return err(400, "invalid_input", "query (1-1200 chars) required");
125
+ try {
126
+ const hits = await federate(c.env, query);
127
+ return json({
128
+ hits: hits.map((h) => ({
129
+ id: h.id,
130
+ score: h.score,
131
+ metadata: { ...h.metadata, chunk_text: undefined },
132
+ text: h.text,
133
+ })),
134
+ });
135
+ } catch {
136
+ return err(503, "retrieval_unavailable", "Federated retrieval is briefly busy.");
137
+ }
138
+ }
139
+
140
+ // the same declarative route table the public worker dispatches through
141
+ // (TODO.impl/31) — one HTTP idiom across workers.
142
+ const ROUTES: Route[] = [
143
+ { method: "GET", pattern: "/health", handler: healthRoute },
144
+ { method: "POST", pattern: "/admin/sync", handler: adminSyncRoute },
145
+ { method: "POST", pattern: "/retrieve", handler: retrieveRoute },
146
+ { method: "POST", pattern: "/api/retrieve", handler: retrieveRoute },
147
+ ];
148
+
149
+ export default {
150
+ async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
151
+ const matched = matchRoute(ROUTES, req.method, new URL(req.url).pathname);
152
+ if (matched) {
153
+ return matched.route.handler({ env, req, ctx, url: new URL(req.url), path: new URL(req.url).pathname, params: matched.params });
154
+ }
155
+ return err(404, "not_found", "Unknown route");
156
+ },
157
+ };
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "types": ["@cloudflare/workers-types"],
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "skipLibCheck": true,
11
+ "isolatedModules": true,
12
+ "forceConsistentCasingInFileNames": true
13
+ },
14
+ "include": ["src/**/*.ts"]
15
+ }
@@ -0,0 +1,32 @@
1
+ name = "rag-internal"
2
+ main = "src/index.ts"
3
+ compatibility_date = "2024-09-23"
4
+ account_id = "06cad8ae9a017c856ab496c6bca9a9d8"
5
+
6
+ [observability]
7
+ enabled = true
8
+
9
+ [[vectorize]]
10
+ binding = "PUBLIC"
11
+ index_name = "idx_oiml_public_v2"
12
+
13
+ [[vectorize]]
14
+ binding = "INTERNAL"
15
+ index_name = "idx_iso_internal"
16
+
17
+ [ai]
18
+ binding = "AI"
19
+
20
+ [[kv_namespaces]]
21
+ id = "0f65d12cc77c41b5b91086de1c04f6da"
22
+ binding = "CACHE"
23
+
24
+ [vars]
25
+ INDEX_VERSION = "internal-v2-retrieve-only"
26
+ SESSION_SECRET_SET = "true"
27
+
28
+ # deploy on internal.oimlsmart.org (custom domain)
29
+ routes = [
30
+ { pattern = "internal.oimlsmart.org", custom_domain = true }
31
+ ]
32
+ workers_dev = true
@@ -0,0 +1,175 @@
1
+ // MCP server (streamable HTTP transport) exposing the OIML public corpus
2
+ // to MCP clients: tools oiml_search and oiml_ask, proxied to the rag-public
3
+ // API. Audience isolation stays enforced in rag-public (this worker holds
4
+ // no index bindings and no secrets beyond an optional API key).
5
+ //
6
+ // Protocol: JSON-RPC 2.0 over POST /mcp (Streamable HTTP). Stateless
7
+ // server — each request is answered in one JSON response; no sessions.
8
+ // https://modelcontextprotocol.io spec (2025-06 streamable HTTP).
9
+
10
+ export interface Env {
11
+ RAG_BASE: string;
12
+ RAG_API_KEY?: string;
13
+ /** read-only access to the derived documents registry (public OIML
14
+ * metadata: editions, active flags, supersession) */
15
+ DB: D1Database;
16
+ }
17
+
18
+ const PROTOCOL_VERSION = "2025-06-18";
19
+
20
+ const json = (body: unknown, status = 200, extra: Record<string, string> = {}) =>
21
+ new Response(JSON.stringify(body), {
22
+ status,
23
+ headers: { "content-type": "application/json", ...extra },
24
+ });
25
+
26
+ const rpcResult = (id: unknown, result: unknown) => json({ jsonrpc: "2.0", id, result });
27
+ const rpcError = (id: unknown, code: number, message: string) =>
28
+ json({ jsonrpc: "2.0", id, error: { code, message } });
29
+
30
+ const TOOLS = [
31
+ {
32
+ name: "oiml_search",
33
+ description:
34
+ "Search the OIML publications corpus (legal metrology: Recommendations R, Documents D, Basic publications B, Guides G). Returns ranked passages with publication identifier, edition, clause and snippet.",
35
+ inputSchema: {
36
+ type: "object" as const,
37
+ properties: {
38
+ query: { type: "string", description: "Natural-language search query" },
39
+ top_k: { type: "integer", description: "Number of passages (default 5, max 10)", default: 5 },
40
+ },
41
+ required: ["query"],
42
+ },
43
+ },
44
+ {
45
+ name: "oiml_documents",
46
+ description:
47
+ "Look up the publication registry for an OIML family: every edition with its derived status (in-force/superseded), which edition is ACTIVE (terminal of the successor chain), and supersession links. Use for 'current/latest edition' and edition-history questions.",
48
+ inputSchema: {
49
+ type: "object" as const,
50
+ properties: {
51
+ family: { type: "string", description: "Family key, e.g. 'R-60' (series letter-number)" },
52
+ },
53
+ required: ["family"],
54
+ },
55
+ },
56
+ {
57
+ name: "oiml_ask",
58
+ description:
59
+ "Ask a question about OIML publications and get a grounded, citation-linked answer. Every claim cites the exact publication and clause it comes from.",
60
+ inputSchema: {
61
+ type: "object" as const,
62
+ properties: {
63
+ query: { type: "string", description: "The question" },
64
+ fresh: { type: "boolean", description: "Skip the answer cache and regenerate from the live corpus (default false)" },
65
+ },
66
+ required: ["query"],
67
+ },
68
+ },
69
+ ];
70
+
71
+ async function rag(env: Env, path: string, body: Record<string, unknown>): Promise<any> {
72
+ const res = await fetch(`${env.RAG_BASE}${path}`, {
73
+ method: "POST",
74
+ headers: {
75
+ "content-type": "application/json",
76
+ ...(env.RAG_API_KEY ? { authorization: `Bearer ${env.RAG_API_KEY}` } : {}),
77
+ },
78
+ body: JSON.stringify(body),
79
+ });
80
+ if (!res.ok) throw new Error(`rag-public ${path} → ${res.status}`);
81
+ return res.json();
82
+ }
83
+
84
+ function searchResultText(r: any): string {
85
+ const anchor = r.clause_anchor && r.clause_anchor !== "overview" ? ` §${r.clause_anchor}` : "";
86
+ return `${r.docidentifier ?? r.doc_id}${r.edition ? ":" + r.edition : ""}${anchor}${r.clause_title ? " — " + r.clause_title : ""}\n${r.snippet ?? ""}`;
87
+ }
88
+
89
+ async function callTool(env: Env, name: string, args: any): Promise<{ content: Array<{ type: string; text: string }> }> {
90
+ if (name === "oiml_search") {
91
+ const query = String(args?.query ?? "").slice(0, 2000);
92
+ if (!query) throw new Error("query is required");
93
+ const data = await rag(env, "/api/search", { query, top_k: Math.min(10, Math.max(1, Number(args?.top_k) || 5)) });
94
+ const text = (data.results ?? []).map(searchResultText).join("\n\n") || "No passages matched.";
95
+ return { content: [{ type: "text", text }] };
96
+ }
97
+ if (name === "oiml_documents") {
98
+ const family = String(args?.family ?? "").trim().slice(0, 20);
99
+ if (!/^[A-Z]-\d{1,3}$/i.test(family)) throw new Error("family must look like 'R-60'");
100
+ const rows = await env.DB.prepare(
101
+ "SELECT d.docidentifier, d.derived_status, d.active, s.docidentifier AS succ FROM documents d LEFT JOIN documents s ON d.superseded_by = s.canonical_id WHERE d.family = ?1 ORDER BY d.part, d.edition",
102
+ )
103
+ .bind(family.toUpperCase())
104
+ .all();
105
+ const lines = (rows.results ?? []).map(
106
+ (r: any) => `${r.docidentifier} — ${r.derived_status}${r.active ? " [ACTIVE]" : ""}${r.succ ? ` → superseded by ${r.succ}` : ""}`,
107
+ );
108
+ return { content: [{ type: "text", text: lines.join("\n") || `No editions found for ${family}` }] };
109
+ }
110
+ if (name === "oiml_ask") {
111
+ const query = String(args?.query ?? "").slice(0, 2000);
112
+ if (!query) throw new Error("query is required");
113
+ const data = await rag(env, "/api/ask", { query, stream: false, ...(args?.fresh ? { fresh: true } : {}) });
114
+ const cites = (data.citations ?? [])
115
+ .map((c: any) => `${c.docidentifier}${c.clause_anchor ? " §" + c.clause_anchor : ""}`)
116
+ .join(", ");
117
+ const text = `${data.answer ?? ""}${cites ? `\n\nSources: ${cites}` : ""}`;
118
+ return { content: [{ type: "text", text }] };
119
+ }
120
+ throw new Error(`Unknown tool: ${name}`);
121
+ }
122
+
123
+ export default {
124
+ async fetch(req: Request, env: Env): Promise<Response> {
125
+ const url = new URL(req.url);
126
+
127
+ if (req.method === "GET" && url.pathname === "/") {
128
+ return json({
129
+ server: "rag-mcp",
130
+ transport: "streamable-http",
131
+ endpoint: "/mcp",
132
+ tools: TOOLS.map((t) => t.name),
133
+ });
134
+ }
135
+
136
+ if (url.pathname !== "/mcp") return json({ error: "not_found" }, 404);
137
+ if (req.method !== "POST") return json({ error: "method_not_allowed — POST JSON-RPC to /mcp" }, 405);
138
+
139
+ let msg: any;
140
+ try {
141
+ msg = await req.json();
142
+ } catch {
143
+ return rpcError(null, -32700, "Parse error");
144
+ }
145
+
146
+ // notification (no id) — acknowledge with 202, nothing to say
147
+ if (msg?.id === undefined || msg?.id === null) return new Response(null, { status: 202 });
148
+
149
+ try {
150
+ switch (msg.method) {
151
+ case "initialize":
152
+ return rpcResult(msg.id, {
153
+ protocolVersion: PROTOCOL_VERSION,
154
+ capabilities: { tools: {} },
155
+ serverInfo: { name: "rag-mcp", version: "1.0.0", title: "OIML SMART AI — public corpus" },
156
+ });
157
+ case "tools/list":
158
+ return rpcResult(msg.id, { tools: TOOLS });
159
+ case "tools/call": {
160
+ const out = await callTool(env, String(msg.params?.name ?? ""), msg.params?.arguments ?? {});
161
+ return rpcResult(msg.id, out);
162
+ }
163
+ case "ping":
164
+ return rpcResult(msg.id, {});
165
+ default:
166
+ return rpcError(msg.id, -32601, `Method not found: ${msg.method}`);
167
+ }
168
+ } catch (e: any) {
169
+ return rpcResult(msg.id, {
170
+ content: [{ type: "text", text: `Error: ${String(e?.message ?? e)}` }],
171
+ isError: true,
172
+ });
173
+ }
174
+ },
175
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "types": ["@cloudflare/workers-types"],
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src/**/*.ts"]
13
+ }
@@ -0,0 +1,18 @@
1
+ name = "rag-mcp"
2
+ main = "src/index.ts"
3
+ compatibility_date = "2026-08-01"
4
+ workers_dev = true
5
+
6
+ # MCP server (audience: public). Proxies retrieval/answering to the
7
+ # rag-public worker over the public API — no direct index bindings here,
8
+ # so corpus isolation stays enforced in ONE place.
9
+ [vars]
10
+ RAG_BASE = "https://ai.oimlsmart.org"
11
+
12
+ [[d1_databases]]
13
+ binding = "DB"
14
+ database_name = "rag-public"
15
+ database_id = "655cd63f-1826-4161-bfd1-90c584add4ce"
16
+
17
+ [observability]
18
+ enabled = true
@@ -0,0 +1,22 @@
1
+ -- Conversations for signed-in members (TODO.impl/15). Anonymous history
2
+ -- is device-local by design; the server stores member conversations only.
3
+ CREATE TABLE IF NOT EXISTS conversations (
4
+ id TEXT PRIMARY KEY,
5
+ sub TEXT NOT NULL,
6
+ title TEXT NOT NULL DEFAULT '',
7
+ created_at TEXT NOT NULL,
8
+ updated_at TEXT NOT NULL
9
+ );
10
+ CREATE INDEX IF NOT EXISTS idx_conversations_sub ON conversations(sub, updated_at DESC);
11
+
12
+ CREATE TABLE IF NOT EXISTS messages (
13
+ id TEXT PRIMARY KEY,
14
+ conversation_id TEXT NOT NULL,
15
+ role TEXT NOT NULL CHECK (role IN ('user','assistant')),
16
+ content TEXT NOT NULL,
17
+ citations TEXT,
18
+ model TEXT,
19
+ created_at TEXT NOT NULL,
20
+ FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
21
+ );
22
+ CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation_id, created_at);
@@ -0,0 +1,9 @@
1
+ -- Public read-only shared conversations (TODO.rag/09)
2
+ CREATE TABLE IF NOT EXISTS shared_conversations (
3
+ slug TEXT PRIMARY KEY,
4
+ owner_sub TEXT NOT NULL,
5
+ title TEXT NOT NULL DEFAULT '',
6
+ messages TEXT NOT NULL, -- JSON array of {role, content, citations}
7
+ created_at TEXT NOT NULL
8
+ );
9
+ CREATE INDEX IF NOT EXISTS idx_shared_owner ON shared_conversations(owner_sub);
@@ -0,0 +1,16 @@
1
+ -- Graph projection (Stage 6 of the SOTA specs): structural edges from
2
+ -- relaton-data-oiml + concept nodes from the Glossarist vocab datasets.
3
+ -- Populated by ingest/graph.py; read by the retrieval graph lane.
4
+ CREATE TABLE IF NOT EXISTS graph_nodes (
5
+ id TEXT PRIMARY KEY, -- doc:OIML-R-60-1-2017 | family:R-60 | concept:<id>
6
+ kind TEXT NOT NULL, -- doc | family | concept
7
+ label TEXT NOT NULL
8
+ );
9
+ CREATE TABLE IF NOT EXISTS graph_edges (
10
+ src TEXT NOT NULL,
11
+ dst TEXT NOT NULL,
12
+ kind TEXT NOT NULL, -- part_of | variant_of | successor | amends | defines
13
+ PRIMARY KEY (src, dst, kind)
14
+ );
15
+ CREATE INDEX IF NOT EXISTS idx_graph_edges_src ON graph_edges(src);
16
+ CREATE INDEX IF NOT EXISTS idx_graph_edges_dst ON graph_edges(dst);
@@ -0,0 +1,19 @@
1
+ -- Document identity registry (Stage 1 of the SOTA specs): the SSOT for
2
+ -- edition status. Status is DERIVED, not copied: relaton's status field
3
+ -- is inconsistent (records claim in-force while carrying successors) —
4
+ -- an edition with a successor is superseded regardless; the ACTIVE
5
+ -- edition of a family+part is the terminal node of its successor chain
6
+ -- (no successor, max edition).
7
+ CREATE TABLE IF NOT EXISTS documents (
8
+ canonical_id TEXT PRIMARY KEY, -- doc:OIML-R-60-2021
9
+ docidentifier TEXT NOT NULL, -- OIML R 60:2021
10
+ family TEXT NOT NULL, -- R-60
11
+ part TEXT, -- 1 | 2 | 3 | A | annexes | sup | NULL
12
+ edition TEXT NOT NULL, -- 2021
13
+ status TEXT NOT NULL, -- in-force | superseded | withdrawn
14
+ derived_status TEXT NOT NULL, -- successor-edge derivation result
15
+ active INTEGER NOT NULL, -- 1 = the active edition of family+part
16
+ superseded_by TEXT, -- canonical_id of the successor
17
+ title TEXT
18
+ );
19
+ CREATE INDEX IF NOT EXISTS idx_documents_family ON documents(family, part, active);
@@ -0,0 +1,11 @@
1
+ -- Cross-turn entity memory (G5): resolved entities per conversation so
2
+ -- pronouns/ellipsis in follow-ups resolve O(1) instead of re-deriving
3
+ -- from raw history text every turn.
4
+ CREATE TABLE IF NOT EXISTS conversation_entities (
5
+ conversation_id TEXT NOT NULL,
6
+ entity TEXT NOT NULL, -- display form, e.g. "OIML R 60-1:2021"
7
+ kind TEXT NOT NULL, -- document | term
8
+ ts INTEGER NOT NULL,
9
+ PRIMARY KEY (conversation_id, entity)
10
+ );
11
+ CREATE INDEX IF NOT EXISTS idx_conv_entities ON conversation_entities(conversation_id);
@@ -0,0 +1,43 @@
1
+ -- G-ETSI-1: full-corpus lexical index for BM25 prefilter (arXiv:2604.09868 §II-B5).
2
+ -- Dense-only Vectorize cannot recover exact-jargon misses; FTS5 is the
3
+ -- corpus-wide sparse stage that feeds RRF alongside dense top-K.
4
+
5
+ CREATE TABLE IF NOT EXISTS chunks (
6
+ id TEXT PRIMARY KEY,
7
+ doc_id TEXT NOT NULL,
8
+ docidentifier TEXT,
9
+ doctype TEXT,
10
+ doc_number TEXT,
11
+ edition TEXT,
12
+ language TEXT,
13
+ clause_anchor TEXT,
14
+ clause_title TEXT,
15
+ status TEXT,
16
+ superseded_by TEXT,
17
+ corpus TEXT,
18
+ tier TEXT,
19
+ -- original body for Hit assembly when dense didn't return this id
20
+ text TEXT NOT NULL,
21
+ -- context preamble + body (Anthropic contextual BM25); what FTS indexes
22
+ fts_text TEXT NOT NULL
23
+ );
24
+
25
+ CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
26
+ fts_text,
27
+ content='chunks',
28
+ content_rowid='rowid',
29
+ tokenize = 'porter unicode61'
30
+ );
31
+
32
+ CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
33
+ INSERT INTO chunks_fts(rowid, fts_text) VALUES (new.rowid, new.fts_text);
34
+ END;
35
+ CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
36
+ INSERT INTO chunks_fts(chunks_fts, rowid, fts_text) VALUES ('delete', old.rowid, old.fts_text);
37
+ END;
38
+ CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
39
+ INSERT INTO chunks_fts(chunks_fts, rowid, fts_text) VALUES ('delete', old.rowid, old.fts_text);
40
+ INSERT INTO chunks_fts(rowid, fts_text) VALUES (new.rowid, new.fts_text);
41
+ END;
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_chunks_doc_number ON chunks(doc_number);
@@ -0,0 +1,15 @@
1
+ -- Answer contract v2: typed unit payloads for block rendering.
2
+ -- Served blocks NEVER pass through the LLM — the model emits [[u:<id>]]
3
+ -- references; the worker resolves them here and validates against the
4
+ -- passages actually used (producer payloads, ingest-validated).
5
+
6
+ CREATE TABLE IF NOT EXISTS unit_payloads (
7
+ unit_id TEXT PRIMARY KEY,
8
+ doc_id TEXT NOT NULL,
9
+ docidentifier TEXT,
10
+ edition TEXT,
11
+ clause_anchor TEXT,
12
+ type TEXT NOT NULL, -- table | formula | figure | term | requirement
13
+ payload TEXT NOT NULL -- the MN 116 typed payload, JSON-encoded
14
+ );
15
+ CREATE INDEX IF NOT EXISTS idx_unit_payloads_doc ON unit_payloads(doc_id);
@@ -0,0 +1,8 @@
1
+ -- Contract v2 over the lexical lane: typed chunks (tables, figures,
2
+ -- formulas, terms) must carry their unit identity through BM25 too —
3
+ -- without these columns, a typed chunk arriving via FTS cannot be
4
+ -- referenced [[u:…]], pinned for doc-scoped queries, or protected by
5
+ -- the retyping check (Vectorize metadata already carries them; D1 did not).
6
+
7
+ ALTER TABLE chunks ADD COLUMN unit_id TEXT;
8
+ ALTER TABLE chunks ADD COLUMN block TEXT;
@@ -0,0 +1,7 @@
1
+ -- The per-answer context mark (TODO.ai-platform/02): the transcript marks
2
+ -- the context each answer was grounded in (the panel's declared chip as
3
+ -- the service APPLIED it — the context_applied echo), so a resumed
4
+ -- conversation keeps its honest context lines. NULL means the answer
5
+ -- carries no recorded context (pre-chips history included) — the panel
6
+ -- renders no context line for those rather than guessing one.
7
+ ALTER TABLE messages ADD COLUMN context_applied TEXT;
@@ -0,0 +1,33 @@
1
+ -- The model plane (TODO.ai-platform/05): the SMART Recommendation MODELS
2
+ -- join the retrieval — the packages' machine content (the requirements'
3
+ -- constraints, the applicability rules, the acceptance criteria, the
4
+ -- conformance tests, the term definitions, the subject constraints, the
5
+ -- characteristics, the state machines) indexed alongside the prose corpus.
6
+ -- The content DERIVES from the primmel packages (the SSOT) via the smart
7
+ -- repo's model-plane bundles (browser/public/data/model-plane/*.json,
8
+ -- byte-clean-guarded there); the per-standard source_hash pins WHAT the
9
+ -- index derived from — a package change moves the hash and the freshness
10
+ -- gate (ingest model-plane --check) refuses to call the index current
11
+ -- until a re-index lands.
12
+
13
+ CREATE TABLE IF NOT EXISTS model_nodes (
14
+ standard TEXT NOT NULL, -- oiml-r60
15
+ node_id TEXT NOT NULL, -- /req/metrological/mpe
16
+ kind TEXT NOT NULL, -- requirement | conformance_test | term | constraint | characteristic | state_machine | dimension
17
+ name TEXT,
18
+ clause_doc TEXT, -- urn:oiml:pub:r:60-1:2021 (the provenance's document)
19
+ clause_ref TEXT, -- 5.3.2 (the clause inside it)
20
+ content TEXT NOT NULL, -- the node's JSON (the bundle projection, verbatim)
21
+ content_hash TEXT NOT NULL, -- sha256 of content (drift forensics)
22
+ PRIMARY KEY (standard, node_id)
23
+ );
24
+ CREATE INDEX IF NOT EXISTS idx_model_nodes_kind ON model_nodes(kind);
25
+
26
+ CREATE TABLE IF NOT EXISTS model_plane_meta (
27
+ standard TEXT PRIMARY KEY,
28
+ package TEXT NOT NULL, -- oiml-r60 (the primmel package)
29
+ plane TEXT NOT NULL, -- the bundle shape version (model-plane/1)
30
+ source_hash TEXT NOT NULL, -- the package content hash the index derived from
31
+ node_count INTEGER NOT NULL,
32
+ indexed_at TEXT NOT NULL -- when the index landed (operational truth)
33
+ );
@@ -0,0 +1,38 @@
1
+ -- Schema union (TODO.impl/18): the bootstrap-only tables, so the
2
+ -- migrations-only path (fresh env, d1 migrations apply) yields the SAME
3
+ -- database as schema.sql. IF NOT EXISTS — a no-op where schema.sql ran.
4
+ CREATE TABLE IF NOT EXISTS api_keys (
5
+ id TEXT PRIMARY KEY,
6
+ name TEXT NOT NULL,
7
+ key_hash TEXT NOT NULL UNIQUE,
8
+ day_limit INTEGER NOT NULL DEFAULT 2000,
9
+ created_at TEXT NOT NULL,
10
+ revoked INTEGER NOT NULL DEFAULT 0
11
+ );
12
+
13
+ CREATE TABLE IF NOT EXISTS queries (
14
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
15
+ ts TEXT NOT NULL,
16
+ day TEXT NOT NULL,
17
+ tier TEXT NOT NULL,
18
+ route TEXT,
19
+ model TEXT,
20
+ ok INTEGER,
21
+ answer_chars INTEGER,
22
+ query_hash TEXT,
23
+ lang TEXT
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS spend (
27
+ day TEXT NOT NULL,
28
+ tier TEXT NOT NULL,
29
+ model TEXT NOT NULL,
30
+ requests INTEGER NOT NULL DEFAULT 0,
31
+ PRIMARY KEY (day, tier, model)
32
+ );
33
+
34
+ CREATE TABLE IF NOT EXISTS feedback (
35
+ query_hash TEXT NOT NULL,
36
+ rating INTEGER NOT NULL,
37
+ ts TEXT NOT NULL
38
+ );
@@ -0,0 +1,15 @@
1
+ -- Personalized memory files (#171): per-member context documents the
2
+ -- ask path injects when the user selects them (their own stated facts —
3
+ -- lab setup, preferred units, instrument inventory). Member-scoped;
4
+ -- content is user-authored, never corpus. Selection rides each ask
5
+ -- (body.memories) and salts the answer cache (answercache contract).
6
+ CREATE TABLE IF NOT EXISTS memories (
7
+ id TEXT PRIMARY KEY,
8
+ sub TEXT NOT NULL,
9
+ name TEXT NOT NULL,
10
+ content TEXT NOT NULL,
11
+ enabled INTEGER NOT NULL DEFAULT 1,
12
+ created_at INTEGER NOT NULL,
13
+ updated_at INTEGER NOT NULL
14
+ );
15
+ CREATE INDEX IF NOT EXISTS idx_memories_sub ON memories(sub, updated_at);