@konneal/engine 0.1.0 → 0.1.2

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 (62) hide show
  1. package/README.md +106 -5
  2. package/dist/admin.d.ts +3 -0
  3. package/dist/{chunk-MB74PTRM.js → chunk-35ODH64W.js} +23 -4
  4. package/dist/chunk-CAEHIVG5.js +54 -0
  5. package/dist/{chunk-WWNCWKKC.js → chunk-EHJEELVB.js} +1 -1
  6. package/dist/{chunk-WOGQM7DJ.js → chunk-OCNLV7Q7.js} +2 -2
  7. package/dist/chunk-ROF3Q7UC.js +156 -0
  8. package/dist/codecs.d.ts +22 -0
  9. package/dist/context.d.ts +2 -9
  10. package/dist/modelplane.d.ts +1 -1
  11. package/dist/pipeline.d.ts +8 -1
  12. package/dist/profile.gen.d.ts +19 -0
  13. package/dist/profile2.gen.d.ts +89 -0
  14. package/dist/prompts/conversational.md +1 -1
  15. package/dist/prompts/enrichment.md +2 -2
  16. package/dist/prompts/precision.md +1 -1
  17. package/dist/prompts/research.md +1 -1
  18. package/dist/prompts/system.md +3 -3
  19. package/dist/prompts/understanding.md +4 -4
  20. package/dist/worker_mcp/src/index.d.ts +10 -0
  21. package/dist/worker_mcp/src/index.js +153 -0
  22. package/dist/{config.js → worker_public/src/config.js} +2 -2
  23. package/dist/{index.js → worker_public/src/index.js} +131 -207
  24. package/dist/{profile.js → worker_public/src/profile.js} +1 -1
  25. package/dist/{refusal.js → worker_public/src/refusal.js} +2 -2
  26. package/dist/worker_public/src/requestScope.js +10 -0
  27. package/package.json +16 -9
  28. package/profile/prompts.yaml +11 -0
  29. package/profile/publisher.yaml +9 -0
  30. package/profile/retrieval.yaml +4 -0
  31. package/scripts/gen_profile.mjs +6 -5
  32. package/workers/worker_internal/src/index.ts +1 -1
  33. package/workers/worker_mcp/src/index.ts +42 -29
  34. package/workers/worker_mcp/tsconfig.json +11 -4
  35. package/workers/worker_public/prompts/conversational.md +1 -1
  36. package/workers/worker_public/prompts/enrichment.md +2 -2
  37. package/workers/worker_public/prompts/precision.md +1 -1
  38. package/workers/worker_public/prompts/research.md +1 -1
  39. package/workers/worker_public/prompts/system.md +3 -3
  40. package/workers/worker_public/prompts/understanding.md +4 -4
  41. package/workers/worker_public/src/admin.ts +15 -3
  42. package/workers/worker_public/src/ask.ts +13 -10
  43. package/workers/worker_public/src/bubble.ts +13 -6
  44. package/workers/worker_public/src/codecs.ts +77 -0
  45. package/workers/worker_public/src/config.ts +1 -1
  46. package/workers/worker_public/src/context.ts +6 -26
  47. package/workers/worker_public/src/graph.ts +2 -3
  48. package/workers/worker_public/src/index.ts +5 -3
  49. package/workers/worker_public/src/lib/http.ts +5 -9
  50. package/workers/worker_public/src/livedata.ts +3 -2
  51. package/workers/worker_public/src/modelplane.ts +13 -5
  52. package/workers/worker_public/src/pipeline.ts +28 -13
  53. package/workers/worker_public/src/profile.gen.ts +23 -4
  54. package/workers/worker_public/src/profile2.gen.ts +122 -0
  55. package/workers/worker_public/src/refusal.ts +19 -21
  56. package/workers/worker_public/src/research.ts +4 -2
  57. package/workers/worker_public/src/stages/conceptGraph.ts +3 -2
  58. package/workers/worker_public/src/stages/corpusScope.ts +8 -2
  59. package/workers/worker_public/src/stages/editionCover.ts +2 -4
  60. package/workers/worker_public/src/understand.ts +2 -1
  61. package/dist/chunk-LLWPT2XV.js +0 -49
  62. package/dist/requestScope.js +0 -10
@@ -0,0 +1,77 @@
1
+ // The serving-side identifier codec registry — the TS mirror of
2
+ // ingest/codecs.py. Each publisher has an identifier grammar; the
3
+ // profile declares the codec id and every publisher-specific parse
4
+ // (how a question names a document, how a label reads, how graph node
5
+ // ids are shaped) lives in the codec implementation, never in domain
6
+ // code. The plain-slug codec is the generic floor: no grammar, no
7
+ // doc-number steering — labels are the identifier as given.
8
+ import { P } from "./profile.ts";
9
+
10
+ export interface DocScope {
11
+ doc_number: string;
12
+ edition?: string;
13
+ label: string;
14
+ }
15
+
16
+ export interface RefCodec {
17
+ /** The explicit form: "R 60-1:2021", "urn:…" — null when unparseable. */
18
+ parse(doc: string, edition?: string): DocScope | null;
19
+ /** The gap-tolerant scan over question text — null when nothing names a document. */
20
+ scanQuestion(query: string): DocScope | null;
21
+ /** The graph's node id → document number — null for other shapes. */
22
+ graphDocNumber(nodeId: string): string | null;
23
+ /** A docidentifier's family key ("R-60") for edition steering — null when not of the grammar. */
24
+ familyOf(docidentifier: string): string | null;
25
+ }
26
+
27
+ /** OIML's grammar: type letter (R/D/B/G/E) + 1–3 digits, optional part,
28
+ * optional edition year; the URN provenance form; part numbers are
29
+ * significant (R 60-1), the edition is never part of the number. */
30
+ export const oimlPubid: RefCodec = {
31
+ parse(doc, edition) {
32
+ const m =
33
+ doc.match(/^urn:oiml:pub:([rdbge]):(\d{1,3})(?:-[0-9A-Za-z]+)?(?::(\d{4}))?$/i) ??
34
+ doc.match(/^(?:OIML\s+)?([RDBGE])\s*(\d{1,3})(?:-[0-9A-Za-z]+)?(?::(\d{4}))?$/i);
35
+ if (!m) return null;
36
+ const type = m[1].toUpperCase();
37
+ const ed = edition ?? m[3] ?? undefined;
38
+ return { doc_number: m[2], ...(ed ? { edition: ed } : {}), label: `OIML ${type} ${m[2]}${ed ? `:${ed}` : ""}` };
39
+ },
40
+ scanQuestion(query) {
41
+ const re = /\b(OIML\s+)?([RDBGE])(\s*)0*(\d{1,3})(?:\s*[-–]\s*\d+)?(?:\s*:\s*(\d{4}))?/gi;
42
+ for (const m of query.matchAll(re)) {
43
+ const [, oimlPrefix, letter, gap, digits, edition] = m;
44
+ // a glued single digit is a class/designation ("E2 weights"), never a naming
45
+ if (digits!.length === 1 && !oimlPrefix && !gap) continue;
46
+ const num = String(Number(digits));
47
+ const type = letter!.toUpperCase();
48
+ return { doc_number: num, ...(edition ? { edition } : {}), label: `OIML ${type} ${num}${edition ? `:${edition}` : ""}` };
49
+ }
50
+ return null;
51
+ },
52
+ graphDocNumber(nodeId) {
53
+ const m = nodeId.match(/^doc:OIML-[A-Z]-(\d+)-/);
54
+ return m ? m[1] : null;
55
+ },
56
+ familyOf(di) {
57
+ const m = /^(?:OIML\s+)?([A-Z])\s?(\d{1,3})(?:[-–]([0-9A-Za-z]+))?/.exec(di);
58
+ return m ? `${m[1]}-${m[2]}` : null;
59
+ },
60
+ };
61
+
62
+ /** The generic floor: the identifier is whatever string it is. */
63
+ export const plainSlug: RefCodec = {
64
+ parse: () => null,
65
+ scanQuestion: () => null,
66
+ graphDocNumber: () => null,
67
+ familyOf: () => null,
68
+ };
69
+
70
+ const REGISTRY: Record<string, RefCodec> = {
71
+ "oiml-pubid": oimlPubid,
72
+ "plain-slug": plainSlug,
73
+ };
74
+
75
+ export function refCodec(): RefCodec {
76
+ return REGISTRY[P().publisher.codec] ?? plainSlug;
77
+ }
@@ -212,7 +212,7 @@ export function datasetsFor(session: unknown): unknown[] {
212
212
  description: d.description,
213
213
  enabled: datasetAllowed(d, session),
214
214
  ...(d.session
215
- ? { requires: `the ${d.permission ?? "ai-preview"} permission (id.oimlsmart.org)`, authenticated: !!session }
215
+ ? { requires: `the ${d.permission ?? "ai-preview"} permission (${P().publisher.identity.issuer.replace(/^https?:\/\//, "")})`, authenticated: !!session }
216
216
  : {}),
217
217
  }));
218
218
  }
@@ -73,28 +73,16 @@ export function parseContext(body: any): DeclaredContext | null {
73
73
  return { kind: c.kind, label, ...(route ? { route } : {}), ...(doc ? { doc } : {}), ...(edition ? { edition } : {}) };
74
74
  }
75
75
 
76
- export interface DocScope {
77
- /** the Vectorize doc_number filter value (the publication FAMILY —
78
- * an entity's clause provenance spans parts: R 60-1 requirements,
79
- * R 60-2 tests) */
80
- doc_number: string;
81
- edition?: string;
82
- /** the canonical label form for the echo + the prompt note */
83
- label: string;
84
- }
76
+ import { refCodec, type DocScope } from "./codecs.ts";
77
+ import { P } from "./profile.ts";
78
+ export type { DocScope };
85
79
 
86
80
  /** Parse the two reference forms the estate speaks: the URN the SMART
87
81
  * models carry as clause provenance (urn:oiml:pub:r:60-1:2021) and the
88
82
  * plain docidentifier (OIML R 60-1:2021 / R 60). Part designations
89
83
  * parse but do not narrow the scope (the family IS the scope). */
90
84
  export function parseDocRef(doc: string, edition?: string): DocScope | null {
91
- const m =
92
- doc.match(/^urn:oiml:pub:([rdbge]):(\d{1,3})(?:-[0-9A-Za-z]+)?(?::(\d{4}))?$/i) ??
93
- doc.match(/^(?:OIML\s+)?([RDBGE])\s*(\d{1,3})(?:-[0-9A-Za-z]+)?(?::(\d{4}))?$/i);
94
- if (!m) return null;
95
- const type = m[1].toUpperCase();
96
- const ed = edition ?? m[3] ?? undefined;
97
- return { doc_number: m[2], ...(ed ? { edition: ed } : {}), label: `OIML ${type} ${m[2]}${ed ? `:${ed}` : ""}` };
85
+ return refCodec().parse(doc, edition);
98
86
  }
99
87
 
100
88
  /** Read the FIRST publication the question's own text names, in the
@@ -108,15 +96,7 @@ export function parseDocRef(doc: string, edition?: string): DocScope | null {
108
96
  * A glued single digit is a class/designation ("E2 weights"), never a
109
97
  * naming; part designations parse but do not narrow the family. */
110
98
  export function namedDocumentIn(query: string): DocScope | null {
111
- const re = /\b(OIML\s+)?([RDBGE])(\s*)0*(\d{1,3})(?:\s*[-–]\s*\d+)?(?:\s*:\s*(\d{4}))?/gi;
112
- for (const m of query.matchAll(re)) {
113
- const [, oimlPrefix, letter, gap, digits, edition] = m;
114
- if (digits!.length === 1 && !oimlPrefix && !gap) continue;
115
- const num = String(Number(digits));
116
- const type = letter!.toUpperCase();
117
- return { doc_number: num, ...(edition ? { edition } : {}), label: `OIML ${type} ${num}${edition ? `:${edition}` : ""}` };
118
- }
119
- return null;
99
+ return refCodec().scanQuestion(query);
120
100
  }
121
101
 
122
102
  /** Resolve the declared document against the publications registry: the
@@ -202,7 +182,7 @@ export function contextNote(declared: DeclaredContext | null, scope: DocScope |
202
182
  return undefined;
203
183
  }
204
184
  if (declared.kind === "page") {
205
- return `Context note: the user is viewing ${declared.label || "a page"}${declared.route ? ` (${declared.route})` : ""} in the OIML SMART platform. The passages come from the general corpus; frame procedural guidance for that page when relevant.`;
185
+ return `Context note: the user is viewing ${declared.label || "a page"}${declared.route ? ` (${declared.route})` : ""} in the ${P().publisher.product_name} platform. The passages come from the general corpus; frame procedural guidance for that page when relevant.`;
206
186
  }
207
187
  if (declared.kind === "entity") {
208
188
  return scope
@@ -3,15 +3,14 @@
3
3
  // the graph says are relevant. Mirrors graph.py's node-id format
4
4
  // (doc:OIML-R-60-1-2017, concept:<id>).
5
5
  import type { Env } from "./env";
6
+ import { refCodec } from "./codecs.ts";
6
7
 
7
8
  /** Graph expansion (G8 query lane): map understanding's term / named
8
9
  * document onto the D1 projection (graph_nodes / graph_edges) and return
9
10
  * the doc_numbers the graph says are relevant. Mirrors graph.py's node-id
10
11
  * format (doc:OIML-R-60-1-2017, concept:<id>). */
11
12
  function docNumberOf(nodeId: string): string | null {
12
- // doc:OIML-R-60-1-2017 → "60" | doc:OIML-B-18-2025 → "18"
13
- const m = nodeId.match(/^doc:OIML-[A-Z]-(\d+)-/);
14
- return m ? m[1] : null;
13
+ return refCodec().graphDocNumber(nodeId);
15
14
  }
16
15
 
17
16
  export async function graphExpand(env: Env, u: { term?: string | null; defined_terms?: string[]; docidentifier?: string | null } | null): Promise<string[] | undefined> {
@@ -19,7 +19,7 @@ export type { Env };
19
19
  import { json, err, corsHeaders, withCors, readJson, authenticate, type ApiKey } from "./lib/http";
20
20
 
21
21
  import { handleSearch } from "./search";
22
- import { handleEnrich, handleSectionUnit, handleCaption, handleVectors, handleJudge, handleCreateKey, handleListKeys } from "./admin";
22
+ import { handleEnrich, handleSectionUnit, handleCaption, handleVectors, handleJudge, handleCreateKey, handleListKeys, handleRevokeKey } from "./admin";
23
23
  import { handleResearch } from "./research";
24
24
  import { handleAsk } from "./ask";
25
25
 
@@ -31,6 +31,7 @@ import { handleAsk } from "./ask";
31
31
  // handler's, derived from the path. Mirrored in docs/spec-api.md.
32
32
 
33
33
  import { matchRoute, type RouteContext, type Route } from "./lib/router";
34
+ import { P } from "./profile.ts";
34
35
 
35
36
  async function serveIndexPage(c: RouteContext): Promise<Response> {
36
37
  // HTML pages are served through the worker with must-revalidate so a
@@ -112,7 +113,7 @@ async function tierFor(c: RouteContext): Promise<{ tier: "anon" | "key" | "membe
112
113
  let key: ApiKey | null = null;
113
114
  if (isApi) {
114
115
  key = await authenticate(c.env, c.req);
115
- if (!key) return err(401, "unauthorized", "Provide a valid API key: Authorization: Bearer oiml_...");
116
+ if (!key) return err(401, "unauthorized", `Provide a valid API key: Authorization: Bearer ${P().publisher.id}_...`);
116
117
  }
117
118
  let tier: "anon" | "key" | "member" = isApi ? "key" : "anon";
118
119
  if (!isApi && c.env.SESSION_SECRET && (await sessionFrom(c.req, c.env as any))) tier = "member";
@@ -215,7 +216,7 @@ async function verifyRoute(c: RouteContext): Promise<Response> {
215
216
  const checks = [
216
217
  { name: "quote_anchors", deterministic: true, pass: anchors.violations.length === 0, detail: `${anchors.violations.length} of ${anchors.total} quoted spans absent from the retrieved passages` },
217
218
  { name: "unit_references", deterministic: true, pass: refs.length === validRefs.length, detail: refs.length ? `${validRefs.length}/${refs.length} unit references resolve to served units` : "no unit references" },
218
- { name: "citations_present", deterministic: true, pass: /\[[^\]]*(OIML|ISO)[^\]]*\]/.test(answer), detail: "normative claims should carry a passage citation" },
219
+ { name: "citations_present", deterministic: true, pass: new RegExp(`\\[[^\\]]*(${P().publisher.name})[^\\]]*\\]`).test(answer), detail: "normative claims should carry a passage citation" },
219
220
  ];
220
221
  const faith = await scoreFaithfulness(env.AI, roleModel(env, "grader"), answer, retrieved.hits.map((h: Hit) => h.text));
221
222
  return json({
@@ -421,6 +422,7 @@ export const ROUTES: Route[] = [
421
422
  { method: "POST", pattern: "/v1/admin/judge", handler: (c) => handleJudge(c.env, c.req) },
422
423
  { method: "POST", pattern: "/v1/admin/keys", handler: (c) => handleCreateKey(c.env, c.req) },
423
424
  { method: "GET", pattern: "/v1/admin/keys", handler: (c) => handleListKeys(c.env, c.req) },
425
+ { method: "DELETE", pattern: "/v1/admin/keys/:id", handler: (c) => handleRevokeKey(c.env, c.req, c.params.id) },
424
426
  ];
425
427
 
426
428
  export default {
@@ -2,6 +2,7 @@
2
2
  // the vocabulary every route handler speaks (TODO.impl/23).
3
3
  import { LIMITS, sha256Hex } from "../config";
4
4
  import type { Env } from "../env";
5
+ import { isAllowedBubbleOrigin } from "../bubble.ts";
5
6
 
6
7
  export const json = (body: unknown, status = 200, extra: Record<string, string> = {}) =>
7
8
  new Response(JSON.stringify(body), {
@@ -14,14 +15,9 @@ export const err = (status: number, code: string, message: string) =>
14
15
 
15
16
  export function corsHeaders(req: Request): Record<string, string> {
16
17
  const origin = req.headers.get("origin") ?? "";
17
- const allowed =
18
- origin === "https://oimlsmart.org" ||
19
- /^https:\/\/[a-z0-9-]+\.oimlsmart\.org$/.test(origin) ||
20
- // the local dev posture: the platform and the minisites develop on
21
- // localhost ports against the live service (the bubble bridge admits
22
- // the same class; anon quota is per-IP, member auth needs the token)
23
- /^http:\/\/localhost(:\d{1,5})?$/.test(origin) ||
24
- /^http:\/\/127\.0\.0\.1(:\d{1,5})?$/.test(origin);
18
+ // the publisher origin family (profile-declared) + the local dev
19
+ // posture — the same rule the bubble bridge applies, from one place
20
+ const allowed = isAllowedBubbleOrigin(origin);
25
21
  return allowed
26
22
  ? {
27
23
  "access-control-allow-origin": origin,
@@ -52,7 +48,7 @@ export interface ApiKey {
52
48
  day_limit: number;
53
49
  }
54
50
 
55
- export async function authenticate(env: Env, req: Request): Promise<ApiKey | null> {
51
+ export async function authenticate(env: Pick<Env, "DB">, req: Request): Promise<ApiKey | null> {
56
52
  const auth = req.headers.get("authorization") ?? "";
57
53
  const m = auth.match(/^Bearer\s+(.+)$/i);
58
54
  if (!m) return null;
@@ -25,6 +25,7 @@
25
25
  * dependency-free (context.ts's pattern: the unit tests load it on
26
26
  * plain node's type stripping, which never resolves extensionless
27
27
  * relative imports). */
28
+ import { P } from "./profile.ts";
28
29
  async function sha256Hex(s: string): Promise<string> {
29
30
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(s));
30
31
  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
@@ -174,7 +175,7 @@ export type LiveRead =
174
175
  /** The platform page for a record — the role family decides the console
175
176
  * (the same page the user's own browser would open). */
176
177
  function recordUrl(cfg: LiveDataConfig, roleFamily: string, store: string, row: any): string {
177
- const std = typeof row.standard_id === "string" ? row.standard_id.replace(/^oiml-/, "") : null;
178
+ const std = typeof row.standard_id === "string" ? row.standard_id.replace(new RegExp(`^${P().publisher.id}-`, "i"), "") : null;
178
179
  if (store === "certificates") {
179
180
  if (roleFamily === "applicant") return `${cfg.platformApi}/app/portal/certificates/${row.id}`;
180
181
  if (std) return `${cfg.platformApi}/app/standards/${std}/certificates/${row.id}`;
@@ -245,7 +246,7 @@ export async function readMyAccount(_env: any, cfg: LiveDataConfig, token: strin
245
246
  records.push({
246
247
  store: "applications",
247
248
  id: String(row.id),
248
- label: `Application ${row.application_number ?? row.id}${row.standard_id ? ` — ${String(row.standard_id).replace(/^oiml-/, "").toUpperCase().replace(/^R(\d)/, "R $1")}` : ""}`,
249
+ label: `Application ${row.application_number ?? row.id}${row.standard_id ? ` — ${String(row.standard_id).replace(new RegExp(`^${P().publisher.id}-`, "i"), "").toUpperCase().replace(/^R(\d)/, "R $1")}` : ""}`,
249
250
  url: recordUrl(cfg, family, "applications", row),
250
251
  status: row.status,
251
252
  date: row.submitted_date ?? row.date_of_application,
@@ -29,6 +29,7 @@
29
29
  * slug segments (the packages' identifier shapes — /req/<class>/<id>,
30
30
  * /conf/<class>/<id>, /term/<id>, /constraint/<id>, /characteristic/<id>,
31
31
  * /state-machine/<id>, /dimension/<id>). */
32
+ import { P } from "./profile.ts";
32
33
  const NODE_RE = /(?:^|[\s("'`])\/(req|conf|term|constraint|characteristic|state-machine|dimension)\/([a-z0-9][a-z0-9_-]*(?:\/[a-z0-9][a-z0-9_-]*)?)(?=[\s)"'`,;:.]|$)/i;
33
34
 
34
35
  /** The first model-node id a text names (the declared chip label first,
@@ -46,7 +47,9 @@ export function modelNodeRefIn(text: string | undefined | null): string | null {
46
47
  * carry a plane; anything else resolves null (honest: no model to bind). */
47
48
  export function standardForDocNumber(docNumber: string | undefined): string | null {
48
49
  if (!docNumber) return null;
49
- return ["60", "91", "129", "144"].includes(docNumber) ? `oiml-r${docNumber}` : null;
50
+ const models = P().sources?.models;
51
+ if (!models?.standards?.length || !models?.standard_prefix) return null;
52
+ return (models.standards as string[]).includes(docNumber) ? `${models.standard_prefix}${docNumber}` : null;
50
53
  }
51
54
 
52
55
  export interface BoundModelNode {
@@ -139,7 +142,7 @@ export function modelGroundingBlock(node: BoundModelNode): string {
139
142
  const c = node.content ?? {};
140
143
  const lines: string[] = [];
141
144
  lines.push(
142
- `Model grounding — the OIML SMART model plane (the platform's machine-readable Recommendation model, derived from the Primmel packages, the models' single source of truth):`,
145
+ P().prompts.vars.model_grounding_intro ?? "Model grounding — the model plane's own statement:",
143
146
  );
144
147
  lines.push(`Node: ${node.node_id} (${node.kind.replace(/_/g, " ")}) — ${node.name} [${node.standard}]`);
145
148
  if (node.clause) lines.push(`Provenance: ${node.clause.urn}`);
@@ -183,7 +186,11 @@ export function modelGroundingBlock(node: BoundModelNode): string {
183
186
  export function modelCitation(node: BoundModelNode) {
184
187
  return {
185
188
  doc_id: `model:${node.standard}`,
186
- docidentifier: `OIML SMART model (${node.standard.replace(/^oiml-r/, "R ")})`,
189
+ docidentifier: `${P().publisher.name} SMART model (${(() => {
190
+ const prefix = P().sources?.models?.standard_prefix ?? "";
191
+ const letter = prefix.replace(/^.*-/, "").toUpperCase();
192
+ return String(node.standard).replace(new RegExp(`^${prefix}`, "i"), `${letter} `);
193
+ })()})`,
187
194
  edition: "",
188
195
  language: "en",
189
196
  clause_anchor: node.clause?.ref || "model",
@@ -209,5 +216,6 @@ export function modelEcho(node: BoundModelNode) {
209
216
 
210
217
  /** The per-corpus guidance note (config.ts's DATASETS pattern — every
211
218
  * retrieved model-plane chunk carries it, chip or no chip). */
212
- export const MODEL_CORPUS_NOTE =
213
- "Some passages are the OIML SMART model plane (labeled OIML SMART model) — the platform's machine-readable Recommendation models derived from the Primmel packages. Treat their machine limits, applicability rules and acceptance criteria as the model's own statement of them (quote machine limits verbatim); where a model passage and a prose passage disagree, say so explicitly and cite both.";
219
+ export function modelCorpusNote(): string {
220
+ return P().prompts.vars.model_passage_note ?? "";
221
+ }
@@ -12,9 +12,24 @@ import { tableContext } from "./tablecontext";
12
12
  // here so the existing import surface keeps working
13
13
  export { refusalAnswer } from "./refusal";
14
14
 
15
+ /** The interpolation source for every prompt: the profile's declared
16
+ * vars plus the derived publisher tokens. Call sites never build
17
+ * their own var map. */
18
+ export function promptVars(extra: Record<string, string> = {}): Record<string, string> {
19
+ // profile vars are snake_case keys; template tokens are UPPER_SNAKE —
20
+ // the map is built here, once, so no call site can spread the raw
21
+ // keys again (the pre-varianlization bug: the identity token sat
22
+ // unmatched and rendered empty in the system prompt)
23
+ const out: Record<string, string> = { PUBLISHER_NAME: P().publisher.name };
24
+ for (const [k, v] of Object.entries(P().prompts?.vars ?? {})) {
25
+ if (typeof v === "string") out[k.toUpperCase()] = v;
26
+ }
27
+ return { ...out, ...extra };
28
+ }
29
+
15
30
  /** Fill {{TOKEN}} placeholders in a prompt data file. Unknown/empty tokens
16
31
  * resolve to "" so optional lines vanish cleanly. */
17
- function fill(template: string, vars: Record<string, string>): string {
32
+ export function fill(template: string, vars: Record<string, string>): string {
18
33
  return template.replace(/\{\{(\w+)\}\}/g, (_m, k: string) => (k in vars ? vars[k] : ""));
19
34
  }
20
35
  import { QueryFilters, toVectorizeFilter } from "./selfquery";
@@ -168,7 +183,7 @@ export function identityNote(member: boolean): string {
168
183
  const upsell = locked.length
169
184
  ? `Signed-in members additionally search: ${locked.map((d) => `${d.label} (${d.description})`).join("; ")}.`
170
185
  : "";
171
- return fill(conversationalPromptText, { CORPORA: corpora, UPSELL: upsell })
186
+ return fill(conversationalPromptText, promptVars({ CORPORA: corpora, UPSELL: upsell }))
172
187
  .split("\n")
173
188
  .filter((l) => l.trim())
174
189
  .join("\n");
@@ -260,14 +275,13 @@ export function buildMessages(
260
275
 
261
276
  // the prompt itself is data (prompts/system.md); one rule per line,
262
277
  // joined with spaces exactly as the original array form
263
- const system = fill(systemPromptText, {
264
- ...P().prompts.vars,
278
+ const system = fill(systemPromptText, promptVars({
265
279
  HISTORY_CONTEXT: history.length
266
280
  ? " Earlier turns of this conversation are provided for context — answer the LATEST question, treating the passages below as the source of truth for facts and citations."
267
281
  : "",
268
282
  CORPUS_NOTES: corpusNotes,
269
283
  LANG_CLAUSE: lang ? ` (explicitly requested: ${lang})` : "",
270
- })
284
+ }))
271
285
  .split("\n")
272
286
  .map((l) => l.trim())
273
287
  .filter(Boolean)
@@ -348,12 +362,13 @@ export function buildMessages(
348
362
  };
349
363
  }
350
364
 
351
- function oimlPublicationUrl(meta: ChunkMeta): string | undefined {
352
- if (!meta.doctype || !meta.doc_number) return undefined;
353
- const typeMap: Record<string, string> = { R: "r", D: "d", B: "b", G: "g", E: "e" };
354
- const t = typeMap[meta.doctype];
355
- if (!t) return undefined;
356
- return `https://www.oiml.org/en/publications/${t}${meta.doc_number}`;
365
+ /** The publisher's catalog page for a publication, from the profile's
366
+ * URL template ({type} = lowercase doctype, then the number); no
367
+ * template, no catalog link. */
368
+ function publicationUrl(meta: ChunkMeta): string | undefined {
369
+ const tpl = P().publisher.catalog_url_template;
370
+ if (!tpl || !meta.doctype || !meta.doc_number) return undefined;
371
+ return tpl.replace("{type}", meta.doctype.toLowerCase()) + meta.doc_number;
357
372
  }
358
373
 
359
374
  export function citations(hits: Hit[]) {
@@ -368,8 +383,8 @@ export function citations(hits: Hit[]) {
368
383
  clause_title: h.metadata.clause_title,
369
384
  status: h.metadata.status ?? "unknown",
370
385
  superseded_by: h.metadata.superseded_by || undefined,
371
- corpus: h.metadata.corpus || "oiml",
372
- url: oimlPublicationUrl(h.metadata),
386
+ corpus: h.metadata.corpus || P().publisher.id,
387
+ url: publicationUrl(h.metadata),
373
388
  snippet: h.text.slice(0, 400),
374
389
  score: h.rerank_score ?? h.score,
375
390
  }))
@@ -8,12 +8,21 @@ export const PROFILE = {
8
8
  "product_name": "Fixture Answers",
9
9
  "description": "A minimal publisher profile exercising every declared surface: an open dataset, a permission-gated dataset, production and lane corpora, prompt vars and retrieval vocabulary.",
10
10
  "domains": {
11
- "public": "fixture.example.org"
11
+ "public": "fixture.example.org",
12
+ "origin_suffix": "fixture.example.org"
12
13
  },
13
14
  "identity": {
14
15
  "issuer": "https://id.fixture.example.org"
15
16
  },
16
- "codec": "plain-slug"
17
+ "codec": "plain-slug",
18
+ "session_cookie": "fixture-session",
19
+ "references": {
20
+ "label_prefix": ""
21
+ },
22
+ "features": {
23
+ "drafts": false,
24
+ "model_plane": false
25
+ }
17
26
  },
18
27
  "datasets": [
19
28
  {
@@ -90,12 +99,22 @@ export const PROFILE = {
90
99
  ]
91
100
  },
92
101
  "retrieval": {
93
- "process_expansion": " fixture certification system framework application evaluation"
102
+ "process_expansion": " fixture certification system framework application evaluation",
103
+ "process_note": "Retrieval note: these passages come from the fixture certification system documents because they govern application procedures for fixture publications."
94
104
  },
95
105
  "prompts": {
96
106
  "vars": {
97
107
  "assistant_identity": "the fixture assistant — a public service answering questions about the fixture publisher's documents",
98
- "refusal_sentence": "I don't have information on this in the indexed fixture documents."
108
+ "refusal_sentence": "I don't have information on this in the indexed fixture documents.",
109
+ "account_note_source": "the user's own fixture account",
110
+ "corpus_kind": "a fixture corpus publication",
111
+ "corpus_kind_plural": "fixture corpus publications",
112
+ "cite_example": "FIXTURE 1:2024 §2.1",
113
+ "cite_quote_example": "FIXTURE 1:2024 §2.1: \"the limit shall not exceed one interval\"",
114
+ "parts_example": "FIXTURE 1-1, FIXTURE 1-A",
115
+ "docid_example": "FIXTURE 1-2",
116
+ "spelling_examples": "\"f1\", \"FIXTURE 1\"",
117
+ "process_vocab": "the fixture certification system framework"
99
118
  }
100
119
  }
101
120
  } as const;
@@ -0,0 +1,122 @@
1
+ // GENERATED from profile2/*.yaml — regenerate: node scripts/gen_profile.mjs
2
+ // (never edit; the drift test compares this file to the sources)
3
+ export const PROFILE = {
4
+ "publisher": {
5
+ "id": "atlas",
6
+ "name": "Atlas",
7
+ "full_name": "The Atlas Standards Institute",
8
+ "product_name": "Atlas Answers",
9
+ "description": "The second fixture publisher of the reference matrix: a technical-standards institute issuing specifications and errata, with a working-group corpus behind a session permission.",
10
+ "domains": {
11
+ "public": "answers.atlas.example",
12
+ "origin_suffix": "atlas.example"
13
+ },
14
+ "identity": {
15
+ "issuer": "https://identity.atlas.example"
16
+ },
17
+ "codec": "plain-slug",
18
+ "session_cookie": "atlas-session",
19
+ "references": {
20
+ "label_prefix": "ATLAS"
21
+ },
22
+ "production": [
23
+ "spec",
24
+ "errata",
25
+ "model"
26
+ ],
27
+ "lanes": {
28
+ "review": [
29
+ "review"
30
+ ],
31
+ "glossary": [
32
+ "glossary"
33
+ ]
34
+ },
35
+ "features": {
36
+ "drafts": false,
37
+ "model_plane": false
38
+ }
39
+ },
40
+ "datasets": [
41
+ {
42
+ "id": "spec",
43
+ "label": "Atlas Specifications",
44
+ "description": "The institute's published specifications and errata",
45
+ "corpora": [
46
+ "spec",
47
+ "errata"
48
+ ],
49
+ "note": "Some passages come from the Atlas errata corpus — cite them the same way as every other passage."
50
+ },
51
+ {
52
+ "id": "wg",
53
+ "label": "Working-group corpus",
54
+ "description": "An access-restricted corpus proving the permission gate",
55
+ "session": true,
56
+ "permission": "committee",
57
+ "corpora": [
58
+ "wg"
59
+ ]
60
+ }
61
+ ],
62
+ "corpora": {
63
+ "production": [
64
+ "spec",
65
+ "errata",
66
+ "model"
67
+ ],
68
+ "lanes": {
69
+ "review": [
70
+ "review"
71
+ ],
72
+ "glossary": [
73
+ "glossary"
74
+ ]
75
+ },
76
+ "corpora": {
77
+ "spec": {
78
+ "repo": "fixtures/matrix2",
79
+ "note": "the second fixture corpus"
80
+ }
81
+ },
82
+ "bibliography": {},
83
+ "terminology": {},
84
+ "models": {}
85
+ },
86
+ "sources": {
87
+ "corpora": {},
88
+ "bibliography": {},
89
+ "terminology": {},
90
+ "models": {}
91
+ },
92
+ "ui": {
93
+ "suggestions": [
94
+ "What does ATLAS 12 specify?",
95
+ "Which errata are open?"
96
+ ],
97
+ "models_disclosure": [
98
+ {
99
+ "role": "Answers",
100
+ "model": "atlas-answer-model"
101
+ }
102
+ ],
103
+ "smoke": [
104
+ {
105
+ "label": "sanity",
106
+ "query": "What does ATLAS 12 specify?",
107
+ "expect": "ATLAS"
108
+ }
109
+ ]
110
+ },
111
+ "retrieval": {
112
+ "process_expansion": " atlas review procedure errata committee specification",
113
+ "process_note": "Retrieval note: these passages come from the Atlas review-procedure documents because they govern the institute's publication process."
114
+ },
115
+ "prompts": {
116
+ "vars": {
117
+ "assistant_identity": "the Atlas Answers assistant — a public service answering questions about the Atlas Standards Institute's specifications",
118
+ "refusal_sentence": "I don't have information on this in the indexed Atlas specifications.",
119
+ "account_note_source": "the user's own Atlas Answers account"
120
+ }
121
+ }
122
+ } as const;
@@ -8,27 +8,24 @@ export function refusalAnswer(): string {
8
8
 
9
9
  // the model occasionally paraphrases the refusal sentence ("...information
10
10
  // on how to make lasagna in the indexed..."); the API contract is the
11
- // exact canonical sentence — normalize variants, keep the redirect tail
12
- const REFUSAL_VARIANT = /^\s*I don[’']?t have information on .{1,120}? in the indexed OIML(?: \w+){0,2} (?:publications|passages|documents|corpus)\.?/i;
13
-
14
- // Upstream wording drift (rag#88, the golden refusal pin recalibrated
15
- // 2026-09-01): the model now also refuses with no pinned sentence at all —
16
- // a "can't help" redirect naming the OIML publications, or a "no real
17
- // answer to give" paraphrase. The runtime and the harness pins
18
- // (scripts/eval.mjs, tests/e2e.mjs, tests/eval-suite.mjs) carry the SAME
19
- // family: every shape the tests accept canonicalizes to the pinned
20
- // sentence here, and nothing else does.
21
- const REFUSAL_DRIFT: RegExp[] = [
22
- /\b(can'?t|cannot|couldn'?t|unable)\b[^.]{0,120}?\b(indexed )?OIML(?: \w+){0,2} publications\b/i,
23
- /\bno real answer to give\b[^.]{0,120}?\bOIML\b/i,
24
- /\b(?:falls|well) outside\b[^.]{0,120}?\b(?:what I can answer|my scope|the scope of)\b/i,
25
- /\boutside (?:of )?what (?:I|this service) can answer\b/i,
26
- // "I can't answer that — weather forecasting is outside my scope":
27
- // requires the refusal verb, so a scope DISCUSSION inside a real answer
28
- // ("this exemption is outside the scope of R 60") never matches
29
- /\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
30
- /^\s*I don[’']?t have any indexed OIML \w+(?:s)? (?:covering|about|on)\b/im,
31
- ];
11
+ // exact canonical sentence — normalize variants, keep the redirect tail.
12
+ // The patterns are built per call: the publisher token is profile data.
13
+ function refusalPatterns(publisher: string): { variant: RegExp; drift: RegExp[] } {
14
+ return {
15
+ variant: new RegExp(`^\\s*I don[’']?t have information on .{1,120}? in the indexed ${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\.?`, "i"),
16
+ drift: [
17
+ new RegExp(`\\b(can'?t|cannot|couldn'?t|unable)\\b[^.]{0,120}?\\b(indexed )?${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\b`, "i"),
18
+ new RegExp(`\\bno real answer to give\\b[^.]{0,120}?\\b${publisher}\\b`, "i"),
19
+ /\b(?:falls|well) outside\b[^.]{0,120}?\b(?:what I can answer|my scope|the scope of)\b/i,
20
+ /\boutside (?:of )?what (?:I|this service) can answer\b/i,
21
+ // "I can't answer that — weather forecasting is outside my scope":
22
+ // requires the refusal verb, so a scope DISCUSSION inside a real answer
23
+ // ("this exemption is outside the scope of R 60") never matches
24
+ /\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
25
+ new RegExp(`^\\s*I don[’']?t have any indexed ${publisher} \\w+(?:s)? (?:covering|about|on)\b`, "im"),
26
+ ],
27
+ };
28
+ }
32
29
 
33
30
  /** Start of the sentence containing offset `i` (after the nearest ". ",
34
31
  * "! ", "? ", or newline before it, else the string start). */
@@ -54,6 +51,7 @@ function sentenceEnd(answer: string, i: number): number {
54
51
  export function canonicalRefusal(answer: string): string {
55
52
  const CANON = refusalAnswer();
56
53
  if (answer.includes(CANON)) return answer;
54
+ const { variant: REFUSAL_VARIANT, drift: REFUSAL_DRIFT } = refusalPatterns(P().publisher.name);
57
55
  const variant = answer.match(REFUSAL_VARIANT);
58
56
  if (variant) return answer.replace(variant[0], CANON);
59
57
  for (const drift of REFUSAL_DRIFT) {