@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
package/README.md CHANGED
@@ -6,8 +6,109 @@ typed tables/formulas/figures, conformance checking by execution, and
6
6
  the measurement gates that keep it honest. Konneal builds the databases
7
7
  and serves the API; publishers own their content, profile and frontend.
8
8
 
9
- Architecture and plan: docs/konneal-extraction-plan.md (and
10
- docs/multi-sdo-architecture.md). The reference deployment is OIML SMART
11
- AI (oimlsmart/ai — the reference profile). This repository was born
12
- from it with history preserved (git filter-repo over the engine
13
- subtrees); the README seed commit was replaced by the extraction.
9
+ ```
10
+ npm install @konneal/engine
11
+ ```
12
+
13
+ BSD-3-Clause. The reference deployment is
14
+ [OIML SMART AI](https://ai.oimlsmart.org).
15
+
16
+ ## What a deployment looks like
17
+
18
+ A publisher's worker is ten lines:
19
+
20
+ ```ts
21
+ // workers/worker_public/src/index.ts — the deployment entry
22
+ import worker, { setProfile } from "@konneal/engine";
23
+ import { PROFILE } from "./profile.gen.ts";
24
+
25
+ setProfile(PROFILE);
26
+
27
+ export default worker;
28
+ ```
29
+
30
+ Everything the publisher owns lives in `profile/*.yaml` — identity,
31
+ datasets, corpora, prompts, thresholds, eval cases. The codegen
32
+ (`gen_profile.mjs`, included) turns the YAML into a committed
33
+ TypeScript module; a drift test keeps both sides honest. The engine
34
+ reads the profile at request time; no publisher fact is hardcoded.
35
+
36
+ The engine also ships the ingest CLI (Python): parse → embed → upsert,
37
+ reading the same profile for its corpora declarations.
38
+
39
+ ## The three packages
40
+
41
+ | Package | What it is |
42
+ |---|---|
43
+ | `@konneal/engine` | The serving Worker + the ingest CLI (this package) |
44
+ | `@konneal/client` | The publisher-site contract: wire types, SSE client, citation chips, typed blocks |
45
+ | `@konneal/create-publisher` | `npm create @konneal/publisher` — scaffolds the whole deployment |
46
+
47
+ Start with the scaffolder: `npm create @konneal/publisher my-sdo
48
+ -- --with-site` writes the profile, the worker entry, the Cloudflare
49
+ wiring, the profile codegen, and (with `--with-site`) a minimal
50
+ Astro+Vue frontend consuming `@konneal/client`.
51
+
52
+ ## The profile (the publisher's single edit surface)
53
+
54
+ | File | Declares |
55
+ |---|---|
56
+ | `publisher.yaml` | id, name, domains, identity issuer, codec, session cookie, features |
57
+ | `datasets.yaml` | what users can scope to (public vs session-gated), per-corpus prompt notes |
58
+ | `corpora.yaml` | the corpus registry — which corpora exist, which indexes serve them |
59
+ | `sources.yaml` | upstream repos the tooling reads (clean corpus, bibliography, terminology, models) |
60
+ | `ui.yaml` | suggestions, model disclosure, smoke probes |
61
+ | `retrieval.yaml` | steering vocabulary, process-intent notes |
62
+ | `prompts.yaml` | the publisher's prompt voice (identity, refusal, examples) |
63
+ | `evals/` | golden cases, annealment probes, ladder — the promotion-gate data |
64
+
65
+ ## Serving architecture
66
+
67
+ - **Retrieval** is a stage registry; the ask path owns composition only
68
+ - **HTTP** is a route table dispatched by both workers
69
+ - Every steering number lives in `THRESHOLDS`; the chunk wire type is
70
+ shared with the Python producer by a contract test
71
+ - **Ports** (`ports/`): ModelRunner, VectorIndex, Kv, Blobs, Runtime —
72
+ the Cloudflare adapters are the only provider-typed module; a purity
73
+ lint enforces it in CI
74
+ - **Publisher purity**: a CI lint fails any publisher string in
75
+ `workers/` outside the codec registry (the identifier grammars)
76
+ - **Feature gates**: `drafts` and `model_plane` are profile-declared,
77
+ off by default
78
+
79
+ ## The ingest CLI
80
+
81
+ ```bash
82
+ pip install git+https://github.com/konneal/engine.git
83
+ python -m ingest.cli parse # corpora → chunks.jsonl + manifest
84
+ python -m ingest.cli embed # embed via the binding's model
85
+ python -m ingest.cli upsert # push vectors + metadata to Vectorize
86
+ ```
87
+
88
+ ## MCP server
89
+
90
+ The package exports `./mcp` — a ten-line entry per deployment serves
91
+ the publisher's public corpus to MCP clients (agent ecosystems).
92
+ Auth is required: the MCP client presents one of the deployment's API
93
+ keys; the same token rides outbound so spend and quota charge that
94
+ key.
95
+
96
+ ## Promotion gates
97
+
98
+ The engine ships the gate harness: a deployment's golden cases and
99
+ annealment probes live in `profile/evals/`; the gate runs them against
100
+ production before any promotion. Answers are witness-checked (the
101
+ answer must contain the expected span, not just score well).
102
+
103
+ ## Repository
104
+
105
+ - `workers/worker_public/` — the public-facing Worker
106
+ - `workers/worker_mcp/` — the MCP server Worker
107
+ - `workers/shared/` — the router, chunk wire type, session
108
+ - `ports/` — the provider seam (interfaces + Cloudflare adapters)
109
+ - `profile/` — the engine's fixture profile (a real deployment carries
110
+ its own; this one exists so tests run against declared data)
111
+ - `profile2/` — the reference matrix's second fixture (a different
112
+ publisher shape; CI proves the engine serves declared profiles)
113
+ - `ingest/` — the Python ingest CLI
114
+ - `tests/` — unit suites (plain node, no network)
package/dist/admin.d.ts CHANGED
@@ -24,3 +24,6 @@ export declare function handleVectors(env: Env, req: Request): Promise<Response>
24
24
  export declare function handleJudge(env: Env, req: Request): Promise<Response>;
25
25
  export declare function handleCreateKey(env: Env, req: Request): Promise<Response>;
26
26
  export declare function handleListKeys(env: Env, req: Request): Promise<Response>;
27
+ /** Revoke an API key (soft: revoked = 1 — the hash row stays for
28
+ * audit; authenticate() already excludes revoked keys). */
29
+ export declare function handleRevokeKey(env: Env, req: Request, id: string): Promise<Response>;
@@ -7,12 +7,21 @@ var PROFILE = {
7
7
  "product_name": "Fixture Answers",
8
8
  "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.",
9
9
  "domains": {
10
- "public": "fixture.example.org"
10
+ "public": "fixture.example.org",
11
+ "origin_suffix": "fixture.example.org"
11
12
  },
12
13
  "identity": {
13
14
  "issuer": "https://id.fixture.example.org"
14
15
  },
15
- "codec": "plain-slug"
16
+ "codec": "plain-slug",
17
+ "session_cookie": "fixture-session",
18
+ "references": {
19
+ "label_prefix": ""
20
+ },
21
+ "features": {
22
+ "drafts": false,
23
+ "model_plane": false
24
+ }
16
25
  },
17
26
  "datasets": [
18
27
  {
@@ -89,12 +98,22 @@ var PROFILE = {
89
98
  ]
90
99
  },
91
100
  "retrieval": {
92
- "process_expansion": " fixture certification system framework application evaluation"
101
+ "process_expansion": " fixture certification system framework application evaluation",
102
+ "process_note": "Retrieval note: these passages come from the fixture certification system documents because they govern application procedures for fixture publications."
93
103
  },
94
104
  "prompts": {
95
105
  "vars": {
96
106
  "assistant_identity": "the fixture assistant \u2014 a public service answering questions about the fixture publisher's documents",
97
- "refusal_sentence": "I don't have information on this in the indexed fixture documents."
107
+ "refusal_sentence": "I don't have information on this in the indexed fixture documents.",
108
+ "account_note_source": "the user's own fixture account",
109
+ "corpus_kind": "a fixture corpus publication",
110
+ "corpus_kind_plural": "fixture corpus publications",
111
+ "cite_example": "FIXTURE 1:2024 \xA72.1",
112
+ "cite_quote_example": 'FIXTURE 1:2024 \xA72.1: "the limit shall not exceed one interval"',
113
+ "parts_example": "FIXTURE 1-1, FIXTURE 1-A",
114
+ "docid_example": "FIXTURE 1-2",
115
+ "spelling_examples": '"f1", "FIXTURE 1"',
116
+ "process_vocab": "the fixture certification system framework"
98
117
  }
99
118
  }
100
119
  };
@@ -0,0 +1,54 @@
1
+ import {
2
+ P
3
+ } from "./chunk-35ODH64W.js";
4
+
5
+ // workers/worker_public/src/refusal.ts
6
+ function refusalAnswer() {
7
+ return P().prompts.vars.refusal_sentence;
8
+ }
9
+ function refusalPatterns(publisher) {
10
+ return {
11
+ variant: new RegExp(`^\\s*I don[\u2019']?t have information on .{1,120}? in the indexed ${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\.?`, "i"),
12
+ drift: [
13
+ new RegExp(`\\b(can'?t|cannot|couldn'?t|unable)\\b[^.]{0,120}?\\b(indexed )?${publisher}(?: \\w+){0,2} (?:publications|passages|documents|corpus)\\b`, "i"),
14
+ new RegExp(`\\bno real answer to give\\b[^.]{0,120}?\\b${publisher}\\b`, "i"),
15
+ /\b(?:falls|well) outside\b[^.]{0,120}?\b(?:what I can answer|my scope|the scope of)\b/i,
16
+ /\boutside (?:of )?what (?:I|this service) can answer\b/i,
17
+ // "I can't answer that — weather forecasting is outside my scope":
18
+ // requires the refusal verb, so a scope DISCUSSION inside a real answer
19
+ // ("this exemption is outside the scope of R 60") never matches
20
+ /\bI can[’']?t answer\b[^.]{0,100}?\bscope\b/i,
21
+ new RegExp(`^\\s*I don[\u2019']?t have any indexed ${publisher} \\w+(?:s)? (?:covering|about|on)\b`, "im")
22
+ ]
23
+ };
24
+ }
25
+ function sentenceStart(answer, i) {
26
+ let s = 0;
27
+ for (const sep of [". ", "! ", "? ", "\n"]) {
28
+ const j = answer.lastIndexOf(sep, i);
29
+ if (j >= 0) s = Math.max(s, j + sep.length);
30
+ }
31
+ return s;
32
+ }
33
+ function sentenceEnd(answer, i) {
34
+ const m = /[.!?\n]/.exec(answer.slice(i));
35
+ return m ? i + m.index + 1 : answer.length;
36
+ }
37
+ function canonicalRefusal(answer) {
38
+ const CANON = refusalAnswer();
39
+ if (answer.includes(CANON)) return answer;
40
+ const { variant: REFUSAL_VARIANT, drift: REFUSAL_DRIFT } = refusalPatterns(P().publisher.name);
41
+ const variant = answer.match(REFUSAL_VARIANT);
42
+ if (variant) return answer.replace(variant[0], CANON);
43
+ for (const drift of REFUSAL_DRIFT) {
44
+ const m = drift.exec(answer);
45
+ if (!m) continue;
46
+ return answer.slice(0, sentenceStart(answer, m.index)) + CANON + answer.slice(sentenceEnd(answer, m.index));
47
+ }
48
+ return answer;
49
+ }
50
+
51
+ export {
52
+ refusalAnswer,
53
+ canonicalRefusal
54
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DATASETS,
3
3
  datasetAllowed
4
- } from "./chunk-WOGQM7DJ.js";
4
+ } from "./chunk-OCNLV7Q7.js";
5
5
 
6
6
  // workers/worker_public/src/requestScope.ts
7
7
  function resolveRequestScope(body, member) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  P
3
- } from "./chunk-MB74PTRM.js";
3
+ } from "./chunk-35ODH64W.js";
4
4
 
5
5
  // workers/worker_public/src/config.ts
6
6
  var MODELS = {
@@ -159,7 +159,7 @@ function datasetsFor(session) {
159
159
  label: d.label,
160
160
  description: d.description,
161
161
  enabled: datasetAllowed(d, session),
162
- ...d.session ? { requires: `the ${d.permission ?? "ai-preview"} permission (id.oimlsmart.org)`, authenticated: !!session } : {}
162
+ ...d.session ? { requires: `the ${d.permission ?? "ai-preview"} permission (${P().publisher.identity.issuer.replace(/^https?:\/\//, "")})`, authenticated: !!session } : {}
163
163
  }));
164
164
  }
165
165
  function SUGGESTIONS() {
@@ -0,0 +1,156 @@
1
+ import {
2
+ LIMITS,
3
+ sha256Hex
4
+ } from "./chunk-OCNLV7Q7.js";
5
+ import {
6
+ P
7
+ } from "./chunk-35ODH64W.js";
8
+
9
+ // workers/worker_public/src/bubble.ts
10
+ function isAllowedBubbleOrigin(origin) {
11
+ const d = P().publisher.domains;
12
+ const suffix = d.origin_suffix ?? (d.public ? d.public.replace(/^[^.]+\./, "") : null);
13
+ if (suffix) {
14
+ const host = origin.startsWith("https://") ? origin.slice("https://".length) : "";
15
+ const labels = host.split(".");
16
+ if (host === suffix) return true;
17
+ if (labels.length >= 3 && labels.slice(1).join(".") === suffix) return true;
18
+ }
19
+ if (/^http:\/\/localhost(:\d{1,5})?$/.test(origin)) return true;
20
+ if (/^http:\/\/127\.0\.0\.1(:\d{1,5})?$/.test(origin)) return true;
21
+ return false;
22
+ }
23
+ function escapeHtml(s) {
24
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
25
+ }
26
+ function bubbleConfirmPage(opts) {
27
+ const who = escapeHtml(opts.name);
28
+ const host = escapeHtml(new URL(opts.origin).host);
29
+ const jsSafe = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
30
+ const payload = jsSafe({
31
+ type: P().publisher.session_cookie ?? `${P().publisher.id}-session`,
32
+ token: opts.token,
33
+ name: opts.name,
34
+ expiresAt: opts.expiresAt
35
+ });
36
+ return `<!DOCTYPE html>
37
+ <html lang="en">
38
+ <head>
39
+ <meta charset="utf-8" />
40
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
41
+ <meta name="robots" content="noindex" />
42
+ <title>${P().publisher.product_name} \u2014 sign in</title>
43
+ <style>
44
+ :root { color-scheme: light dark; }
45
+ body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 2rem 1.25rem;
46
+ background: #faf6ee; color: #0a1628; line-height: 1.5; }
47
+ main { max-width: 26rem; margin: 0 auto; }
48
+ h1 { font-size: 1.15rem; margin: 0 0 0.75rem; }
49
+ p { margin: 0 0 1rem; font-size: 0.925rem; }
50
+ .who { font-weight: 600; }
51
+ .row { display: flex; gap: 0.75rem; margin-top: 1.25rem; }
52
+ button { flex: 1; min-height: 44px; font: inherit; font-weight: 600; border-radius: 6px; cursor: pointer; }
53
+ .go { background: #004996; color: #fff; border: 1px solid #004996; }
54
+ .no { background: transparent; color: inherit; border: 1px solid #ddd2bd; }
55
+ @media (prefers-color-scheme: dark) {
56
+ body { background: #0a1628; color: #f5efe4; }
57
+ .go { background: #89b4ef; color: #001230; border-color: #89b4ef; }
58
+ .no { border-color: #1f3357; }
59
+ }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <main>
64
+ <h1>Continue to the ${P().publisher.product_name} assistant?</h1>
65
+ <p>Signed in as <span class="who">${who}</span>. The page at <span class="who">${host}</span>
66
+ asked to connect the assistant to your account, so your conversations sync there.</p>
67
+ <p>The assistant can read the public corpus and your own assistant conversations \u2014 nothing else.</p>
68
+ <div class="row">
69
+ <button type="button" class="no" id="cancel">Cancel</button>
70
+ <button type="button" class="go" id="go">Continue</button>
71
+ </div>
72
+ <p id="done" hidden>You can close this window.</p>
73
+ </main>
74
+ <script>
75
+ var TARGET = ${jsSafe(opts.origin)};
76
+ var PAYLOAD = ${payload};
77
+ var done = document.getElementById("done");
78
+ document.getElementById("cancel").addEventListener("click", function () { window.close(); done.hidden = false; });
79
+ document.getElementById("go").addEventListener("click", function () {
80
+ if (window.opener) {
81
+ window.opener.postMessage(PAYLOAD, TARGET);
82
+ window.close();
83
+ }
84
+ done.hidden = false;
85
+ });
86
+ if (!window.opener) {
87
+ document.querySelector(".row").hidden = true;
88
+ document.querySelector("h1").textContent = "Signed in.";
89
+ done.hidden = false;
90
+ }
91
+ </script>
92
+ </body>
93
+ </html>`;
94
+ }
95
+
96
+ // workers/worker_public/src/lib/http.ts
97
+ var json = (body, status = 200, extra = {}) => new Response(JSON.stringify(body), {
98
+ status,
99
+ headers: { "content-type": "application/json", ...extra }
100
+ });
101
+ var err = (status, code, message) => json({ error: { code, message } }, status);
102
+ function corsHeaders(req) {
103
+ const origin = req.headers.get("origin") ?? "";
104
+ const allowed = isAllowedBubbleOrigin(origin);
105
+ return allowed ? {
106
+ "access-control-allow-origin": origin,
107
+ // PATCH + DELETE: the conversations API speaks them (rename,
108
+ // delete) — the embedded panel preflights cross-origin.
109
+ "access-control-allow-methods": "GET, POST, PATCH, DELETE, OPTIONS",
110
+ "access-control-allow-headers": "authorization, content-type",
111
+ "access-control-max-age": "86400"
112
+ } : {};
113
+ }
114
+ function withCors(res, cors) {
115
+ if (!cors["access-control-allow-origin"]) return res;
116
+ const headers = new Headers(res.headers);
117
+ for (const [k, v] of Object.entries(cors)) headers.set(k, v);
118
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
119
+ }
120
+ async function authenticate(env, req) {
121
+ const auth = req.headers.get("authorization") ?? "";
122
+ const m = auth.match(/^Bearer\s+(.+)$/i);
123
+ if (!m) return null;
124
+ const keyHash = await sha256Hex(m[1].trim());
125
+ const row = await env.DB.prepare(
126
+ "SELECT id, name, day_limit FROM api_keys WHERE key_hash = ?1 AND revoked = 0"
127
+ ).bind(keyHash).first();
128
+ return row ?? null;
129
+ }
130
+ async function readJson(req) {
131
+ try {
132
+ const body = await req.json();
133
+ if (!body || typeof body !== "object") return null;
134
+ return body;
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+ function validateQuery(body) {
140
+ const query = typeof body?.query === "string" ? body.query.trim() : "";
141
+ if (!query || query.length > LIMITS.maxInputChars) return null;
142
+ const lang = typeof body?.lang === "string" && /^[a-z]{2}$/.test(body.lang) ? body.lang : void 0;
143
+ return { query, lang };
144
+ }
145
+
146
+ export {
147
+ isAllowedBubbleOrigin,
148
+ bubbleConfirmPage,
149
+ json,
150
+ err,
151
+ corsHeaders,
152
+ withCors,
153
+ authenticate,
154
+ readJson,
155
+ validateQuery
156
+ };
@@ -0,0 +1,22 @@
1
+ export interface DocScope {
2
+ doc_number: string;
3
+ edition?: string;
4
+ label: string;
5
+ }
6
+ export interface RefCodec {
7
+ /** The explicit form: "R 60-1:2021", "urn:…" — null when unparseable. */
8
+ parse(doc: string, edition?: string): DocScope | null;
9
+ /** The gap-tolerant scan over question text — null when nothing names a document. */
10
+ scanQuestion(query: string): DocScope | null;
11
+ /** The graph's node id → document number — null for other shapes. */
12
+ graphDocNumber(nodeId: string): string | null;
13
+ /** A docidentifier's family key ("R-60") for edition steering — null when not of the grammar. */
14
+ familyOf(docidentifier: string): string | null;
15
+ }
16
+ /** OIML's grammar: type letter (R/D/B/G/E) + 1–3 digits, optional part,
17
+ * optional edition year; the URN provenance form; part numbers are
18
+ * significant (R 60-1), the edition is never part of the number. */
19
+ export declare const oimlPubid: RefCodec;
20
+ /** The generic floor: the identifier is whatever string it is. */
21
+ export declare const plainSlug: RefCodec;
22
+ export declare function refCodec(): RefCodec;
package/dist/context.d.ts CHANGED
@@ -50,15 +50,8 @@ export declare const NO_CONTEXT: AppliedContext;
50
50
  * malformed degrades to null (no context), never to a 400 — a context
51
51
  * the service can't parse is a context it must not apply. */
52
52
  export declare function parseContext(body: any): DeclaredContext | null;
53
- export interface DocScope {
54
- /** the Vectorize doc_number filter value (the publication FAMILY —
55
- * an entity's clause provenance spans parts: R 60-1 requirements,
56
- * R 60-2 tests) */
57
- doc_number: string;
58
- edition?: string;
59
- /** the canonical label form for the echo + the prompt note */
60
- label: string;
61
- }
53
+ import { type DocScope } from "./codecs.ts";
54
+ export type { DocScope };
62
55
  /** Parse the two reference forms the estate speaks: the URN the SMART
63
56
  * models carry as clause provenance (urn:oiml:pub:r:60-1:2021) and the
64
57
  * plain docidentifier (OIML R 60-1:2021 / R 60). Part designations
@@ -58,4 +58,4 @@ export declare function modelEcho(node: BoundModelNode): {
58
58
  };
59
59
  /** The per-corpus guidance note (config.ts's DATASETS pattern — every
60
60
  * retrieved model-plane chunk carries it, chip or no chip). */
61
- export declare const MODEL_CORPUS_NOTE = "Some passages are the OIML SMART model plane (labeled OIML SMART model) \u2014 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.";
61
+ export declare function modelCorpusNote(): string;
@@ -1,4 +1,11 @@
1
1
  export { refusalAnswer } from "./refusal";
2
+ /** The interpolation source for every prompt: the profile's declared
3
+ * vars plus the derived publisher tokens. Call sites never build
4
+ * their own var map. */
5
+ export declare function promptVars(extra?: Record<string, string>): Record<string, string>;
6
+ /** Fill {{TOKEN}} placeholders in a prompt data file. Unknown/empty tokens
7
+ * resolve to "" so optional lines vanish cleanly. */
8
+ export declare function fill(template: string, vars: Record<string, string>): string;
2
9
  import { QueryFilters } from "./selfquery";
3
10
  import type { RetrieveOptions, GlossaryEntry } from "./stages/types";
4
11
  export type { ChunkMeta, Hit } from "../../shared/chunk";
@@ -50,7 +57,7 @@ export declare function citations(hits: Hit[]): {
50
57
  clause_title: string;
51
58
  status: string;
52
59
  superseded_by: string | undefined;
53
- corpus: string;
60
+ corpus: any;
54
61
  url: string | undefined;
55
62
  snippet: string;
56
63
  score: number;
@@ -7,11 +7,20 @@ export declare const PROFILE: {
7
7
  readonly 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.";
8
8
  readonly domains: {
9
9
  readonly public: "fixture.example.org";
10
+ readonly origin_suffix: "fixture.example.org";
10
11
  };
11
12
  readonly identity: {
12
13
  readonly issuer: "https://id.fixture.example.org";
13
14
  };
14
15
  readonly codec: "plain-slug";
16
+ readonly session_cookie: "fixture-session";
17
+ readonly references: {
18
+ readonly label_prefix: "";
19
+ };
20
+ readonly features: {
21
+ readonly drafts: false;
22
+ readonly model_plane: false;
23
+ };
15
24
  };
16
25
  readonly datasets: readonly [{
17
26
  readonly id: "pub";
@@ -60,11 +69,21 @@ export declare const PROFILE: {
60
69
  };
61
70
  readonly retrieval: {
62
71
  readonly process_expansion: " fixture certification system framework application evaluation";
72
+ readonly process_note: "Retrieval note: these passages come from the fixture certification system documents because they govern application procedures for fixture publications.";
63
73
  };
64
74
  readonly prompts: {
65
75
  readonly vars: {
66
76
  readonly assistant_identity: "the fixture assistant — a public service answering questions about the fixture publisher's documents";
67
77
  readonly refusal_sentence: "I don't have information on this in the indexed fixture documents.";
78
+ readonly account_note_source: "the user's own fixture account";
79
+ readonly corpus_kind: "a fixture corpus publication";
80
+ readonly corpus_kind_plural: "fixture corpus publications";
81
+ readonly cite_example: "FIXTURE 1:2024 §2.1";
82
+ readonly cite_quote_example: "FIXTURE 1:2024 §2.1: \"the limit shall not exceed one interval\"";
83
+ readonly parts_example: "FIXTURE 1-1, FIXTURE 1-A";
84
+ readonly docid_example: "FIXTURE 1-2";
85
+ readonly spelling_examples: "\"f1\", \"FIXTURE 1\"";
86
+ readonly process_vocab: "the fixture certification system framework";
68
87
  };
69
88
  };
70
89
  };
@@ -0,0 +1,89 @@
1
+ export declare const PROFILE: {
2
+ readonly publisher: {
3
+ readonly id: "atlas";
4
+ readonly name: "Atlas";
5
+ readonly full_name: "The Atlas Standards Institute";
6
+ readonly product_name: "Atlas Answers";
7
+ readonly 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.";
8
+ readonly domains: {
9
+ readonly public: "answers.atlas.example";
10
+ readonly origin_suffix: "atlas.example";
11
+ };
12
+ readonly identity: {
13
+ readonly issuer: "https://identity.atlas.example";
14
+ };
15
+ readonly codec: "plain-slug";
16
+ readonly session_cookie: "atlas-session";
17
+ readonly references: {
18
+ readonly label_prefix: "ATLAS";
19
+ };
20
+ readonly production: readonly ["spec", "errata", "model"];
21
+ readonly lanes: {
22
+ readonly review: readonly ["review"];
23
+ readonly glossary: readonly ["glossary"];
24
+ };
25
+ readonly features: {
26
+ readonly drafts: false;
27
+ readonly model_plane: false;
28
+ };
29
+ };
30
+ readonly datasets: readonly [{
31
+ readonly id: "spec";
32
+ readonly label: "Atlas Specifications";
33
+ readonly description: "The institute's published specifications and errata";
34
+ readonly corpora: readonly ["spec", "errata"];
35
+ readonly note: "Some passages come from the Atlas errata corpus — cite them the same way as every other passage.";
36
+ }, {
37
+ readonly id: "wg";
38
+ readonly label: "Working-group corpus";
39
+ readonly description: "An access-restricted corpus proving the permission gate";
40
+ readonly session: true;
41
+ readonly permission: "committee";
42
+ readonly corpora: readonly ["wg"];
43
+ }];
44
+ readonly corpora: {
45
+ readonly production: readonly ["spec", "errata", "model"];
46
+ readonly lanes: {
47
+ readonly review: readonly ["review"];
48
+ readonly glossary: readonly ["glossary"];
49
+ };
50
+ readonly corpora: {
51
+ readonly spec: {
52
+ readonly repo: "fixtures/matrix2";
53
+ readonly note: "the second fixture corpus";
54
+ };
55
+ };
56
+ readonly bibliography: {};
57
+ readonly terminology: {};
58
+ readonly models: {};
59
+ };
60
+ readonly sources: {
61
+ readonly corpora: {};
62
+ readonly bibliography: {};
63
+ readonly terminology: {};
64
+ readonly models: {};
65
+ };
66
+ readonly ui: {
67
+ readonly suggestions: readonly ["What does ATLAS 12 specify?", "Which errata are open?"];
68
+ readonly models_disclosure: readonly [{
69
+ readonly role: "Answers";
70
+ readonly model: "atlas-answer-model";
71
+ }];
72
+ readonly smoke: readonly [{
73
+ readonly label: "sanity";
74
+ readonly query: "What does ATLAS 12 specify?";
75
+ readonly expect: "ATLAS";
76
+ }];
77
+ };
78
+ readonly retrieval: {
79
+ readonly process_expansion: " atlas review procedure errata committee specification";
80
+ readonly process_note: "Retrieval note: these passages come from the Atlas review-procedure documents because they govern the institute's publication process.";
81
+ };
82
+ readonly prompts: {
83
+ readonly vars: {
84
+ readonly assistant_identity: "the Atlas Answers assistant — a public service answering questions about the Atlas Standards Institute's specifications";
85
+ readonly refusal_sentence: "I don't have information on this in the indexed Atlas specifications.";
86
+ readonly account_note_source: "the user's own Atlas Answers account";
87
+ };
88
+ };
89
+ };
@@ -1,4 +1,4 @@
1
- You are the OIML SMART AI assistant at ai.oimlsmart.org, a public service answering questions about OIML legal-metrology publications.
1
+ You are {{ASSISTANT_IDENTITY}}.
2
2
  This turn is conversational — about you, this service, a greeting or small talk — NOT a knowledge question, so there are no context passages.
3
3
  Answer naturally in first person, briefly and warmly, in the language of the user's message. Do not cite sources for this turn and never refuse it.
4
4
  Facts about this service you may speak from:
@@ -1,3 +1,3 @@
1
- You write a retrieval context for a passage from an OIML legal-metrology publication. The context is prepended to the passage before embedding so a semantic search can locate the passage even when the query uses different vocabulary than the passage itself.
1
+ You write a retrieval context for a passage from {{CORPUS_KIND}}. The context is prepended to the passage before embedding so a semantic search can locate the passage even when the query uses different vocabulary than the passage itself.
2
2
 
3
- Write ONE concise sentence (at most 40 words) that situates the passage: name the publication by its exact OIML identifier (including part or annex when applicable) and what the passage covers — paraphrasing the topic in words DIFFERENT from the passage's own. Do not copy the passage verbatim, do not add facts that are not derivable from the passage or its header, do not answer or explain the content. Reply with the context sentence only — no quotes, no preamble.
3
+ Write ONE concise sentence (at most 40 words) that situates the passage: name the publication by its exact {{PUBLISHER_NAME}} identifier (including part or annex when applicable) and what the passage covers — paraphrasing the topic in words DIFFERENT from the passage's own. Do not copy the passage verbatim, do not add facts that are not derivable from the passage or its header, do not answer or explain the content. Reply with the context sentence only — no quotes, no preamble.
@@ -1 +1 @@
1
- You judge CONTEXT PRECISION for a retrieval system over OIML publications. Given the question and the ranked passages (in the order they were presented), score the fraction of passages that contain material USEFUL for answering the question: 1.0 = all useful; 0.5 = half; 0.0 = none. Judge each passage on its own content, not its rank. Reply with ONLY: {"score": 0.0-1.0}
1
+ You judge CONTEXT PRECISION for a retrieval system over {{PUBLISHER_NAME}} publications. Given the question and the ranked passages (in the order they were presented), score the fraction of passages that contain material USEFUL for answering the question: 1.0 = all useful; 0.5 = half; 0.0 = none. Judge each passage on its own content, not its rank. Reply with ONLY: {"score": 0.0-1.0}
@@ -1,4 +1,4 @@
1
- You are a sufficiency judge for a research loop over OIML publications. Given the research question and the passages collected so far (across iterations), decide whether the collected evidence is SUFFICIENT to write a complete, well-grounded answer.
1
+ You are a sufficiency judge for a research loop over {{PUBLISHER_NAME}} publications. Given the research question and the passages collected so far (across iterations), decide whether the collected evidence is SUFFICIENT to write a complete, well-grounded answer.
2
2
 
3
3
  Reply with ONLY a JSON object:
4
4
  {"sufficient": true|false, "missing": "short description of what is still missing (empty string when sufficient)"}