@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.
- package/LICENSE +29 -0
- package/README.md +13 -0
- package/dist/admin.d.ts +26 -0
- package/dist/ai.d.ts +6 -0
- package/dist/anchors.d.ts +6 -0
- package/dist/answercache.d.ts +22 -0
- package/dist/ask.d.ts +5 -0
- package/dist/auth.d.ts +12 -0
- package/dist/bubble.d.ts +14 -0
- package/dist/chunk-LLWPT2XV.js +49 -0
- package/dist/chunk-MB74PTRM.js +114 -0
- package/dist/chunk-WOGQM7DJ.js +197 -0
- package/dist/chunk-WWNCWKKC.js +42 -0
- package/dist/completion.d.ts +5 -0
- package/dist/config.d.ts +154 -0
- package/dist/config.js +37 -0
- package/dist/context.d.ts +115 -0
- package/dist/conversations.d.ts +5 -0
- package/dist/drafts.d.ts +129 -0
- package/dist/env.d.ts +57 -0
- package/dist/faithfulness.d.ts +5 -0
- package/dist/grader.d.ts +3 -0
- package/dist/graph.d.ts +13 -0
- package/dist/hybrid.d.ts +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5373 -0
- package/dist/internal_gateway.d.ts +14 -0
- package/dist/lexical.d.ts +7 -0
- package/dist/livedata.d.ts +77 -0
- package/dist/memories.d.ts +10 -0
- package/dist/modelplane.d.ts +61 -0
- package/dist/oidc.d.ts +73 -0
- package/dist/pipeline.d.ts +57 -0
- package/dist/profile.d.ts +2 -0
- package/dist/profile.gen.d.ts +70 -0
- package/dist/profile.js +8 -0
- package/dist/projects.d.ts +8 -0
- package/dist/prompts/conversational.md +8 -0
- package/dist/prompts/enrichment.md +3 -0
- package/dist/prompts/faithfulness.md +1 -0
- package/dist/prompts/grader.md +5 -0
- package/dist/prompts/listwise.md +3 -0
- package/dist/prompts/precision.md +1 -0
- package/dist/prompts/reflect.md +1 -0
- package/dist/prompts/relevancy.md +1 -0
- package/dist/prompts/research.md +10 -0
- package/dist/prompts/section-summary.md +5 -0
- package/dist/prompts/summarize.md +1 -0
- package/dist/prompts/system.md +18 -0
- package/dist/prompts/understanding.md +17 -0
- package/dist/quota.d.ts +13 -0
- package/dist/reflect.d.ts +5 -0
- package/dist/refs.d.ts +40 -0
- package/dist/refusal.d.ts +9 -0
- package/dist/refusal.js +9 -0
- package/dist/requestScope.d.ts +26 -0
- package/dist/requestScope.js +10 -0
- package/dist/research.d.ts +8 -0
- package/dist/search.d.ts +4 -0
- package/dist/selfquery.d.ts +7 -0
- package/dist/session.d.ts +1 -0
- package/dist/share.d.ts +2 -0
- package/dist/structural.d.ts +27 -0
- package/dist/tablecontext.d.ts +11 -0
- package/dist/understand.d.ts +11 -0
- package/dist/understandContract.d.ts +29 -0
- package/dist/verdict.d.ts +24 -0
- package/docs/API.md +451 -0
- package/docs/ARCHITECTURE.md +302 -0
- package/docs/AUDIT-2026-08-24.md +71 -0
- package/docs/CONTRIBUTOR-AUDIT-2026-08-25.md +147 -0
- package/docs/INGEST-ARCHITECTURE.md +158 -0
- package/docs/MCP.md +92 -0
- package/docs/METANORMA-AI-SERIALIZATION.md +247 -0
- package/docs/MKO-EXPORT-PIPELINE.md +147 -0
- package/docs/REDESIGN-NORMATIVE-RAG-ETSI.md +485 -0
- package/docs/RESEARCH-SOTA-2026.md +243 -0
- package/docs/ROADMAP-SOTA.md +130 -0
- package/docs/SOTA-STAGE-SPECS.md +509 -0
- package/docs/annealment/F1-verdict.md +27 -0
- package/docs/annealment/F10-notes.md +19 -0
- package/docs/annealment/F11-composition.md +17 -0
- package/docs/annealment/F12-passport.md +17 -0
- package/docs/annealment/F2-counterfactual.md +20 -0
- package/docs/annealment/F3-absence.md +21 -0
- package/docs/annealment/F4-instance.md +18 -0
- package/docs/annealment/F5-workflow.md +21 -0
- package/docs/annealment/F6-impact.md +21 -0
- package/docs/annealment/F7-editions.md +18 -0
- package/docs/annealment/F8-selfverify.md +19 -0
- package/docs/annealment/F9-projection-qa.md +17 -0
- package/docs/annealment/L0-locate.md +19 -0
- package/docs/annealment/L1-extract.md +18 -0
- package/docs/annealment/L2-nomenclature.md +22 -0
- package/docs/annealment/L3-geometry.md +23 -0
- package/docs/annealment/L4-composition.md +21 -0
- package/docs/annealment/L5-cross-standard.md +20 -0
- package/docs/annealment/L6-diachrony.md +21 -0
- package/docs/annealment/L7-perception.md +20 -0
- package/docs/annealment/L8-computation.md +22 -0
- package/docs/annealment/L9-instance-process.md +23 -0
- package/docs/annealment/README.md +10 -0
- package/docs/guidelines-metanorma-ai-programme.md +279 -0
- package/docs/identity-onboarding-rag.md +65 -0
- package/docs/identity-service.md +219 -0
- package/docs/knowledge-annealment.md +273 -0
- package/docs/konneal-extraction-plan.md +481 -0
- package/docs/metanorma-for-ai.md +270 -0
- package/docs/mirror-plan.md +36 -0
- package/docs/multi-sdo-architecture.md +191 -0
- package/docs/paper-annealment-comparison.md +259 -0
- package/docs/paper-assets/architecture.svg +94 -0
- package/docs/paper-assets/contract-v2.svg +94 -0
- package/docs/paper-assets/mko-ingest.svg +91 -0
- package/docs/paper-oiml-bulletin.md +402 -0
- package/docs/paper-oiml-bulletin.mdx +419 -0
- package/docs/product-branding-options.md +172 -0
- package/docs/projects-design.md +88 -0
- package/docs/sota-mechanisms.md +184 -0
- package/docs/spec-api.md +77 -0
- package/docs/spec-pipeline.md +126 -0
- package/docs/vector-adapter.md +88 -0
- package/package.json +70 -0
- package/profile/corpora.yaml +5 -0
- package/profile/datasets.yaml +14 -0
- package/profile/prompts.yaml +5 -0
- package/profile/publisher.yaml +17 -0
- package/profile/retrieval.yaml +1 -0
- package/profile/sources.yaml +5 -0
- package/profile/ui.yaml +7 -0
- package/scripts/gen_profile.mjs +33 -0
- package/workers/shared/ai.ts +21 -0
- package/workers/shared/auth.ts +16 -0
- package/workers/shared/chunk.ts +108 -0
- package/workers/shared/oidc.ts +312 -0
- package/workers/shared/router.ts +45 -0
- package/workers/shared/session.ts +104 -0
- package/workers/worker_internal/src/index.ts +157 -0
- package/workers/worker_internal/tsconfig.json +15 -0
- package/workers/worker_internal/wrangler.toml +32 -0
- package/workers/worker_mcp/src/index.ts +175 -0
- package/workers/worker_mcp/tsconfig.json +13 -0
- package/workers/worker_mcp/wrangler.toml +18 -0
- package/workers/worker_public/migrations/0002_conversations.sql +22 -0
- package/workers/worker_public/migrations/0003_shared_conversations.sql +9 -0
- package/workers/worker_public/migrations/0004_graph.sql +16 -0
- package/workers/worker_public/migrations/0005_documents.sql +19 -0
- package/workers/worker_public/migrations/0006_conversation_entities.sql +11 -0
- package/workers/worker_public/migrations/0007_chunks_fts.sql +43 -0
- package/workers/worker_public/migrations/0008_unit_payloads.sql +15 -0
- package/workers/worker_public/migrations/0009_chunks_unit.sql +8 -0
- package/workers/worker_public/migrations/0009_message_context.sql +7 -0
- package/workers/worker_public/migrations/0010_model_nodes.sql +33 -0
- package/workers/worker_public/migrations/0011_schema_union.sql +38 -0
- package/workers/worker_public/migrations/0012_memories.sql +15 -0
- package/workers/worker_public/migrations/0013_projects.sql +21 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2021-11-03/index.d.ts +16306 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2021-11-03/index.ts +16261 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-01-31/index.d.ts +16373 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-01-31/index.ts +16328 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-03-21/index.d.ts +16382 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-03-21/index.ts +16337 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-08-04/index.d.ts +16383 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-08-04/index.ts +16338 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-10-31/index.d.ts +16403 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-10-31/index.ts +16358 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-11-30/index.d.ts +16408 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2022-11-30/index.ts +16363 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2023-03-01/index.d.ts +16414 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2023-03-01/index.ts +16369 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2023-07-01/index.d.ts +16414 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/2023-07-01/index.ts +16369 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/README.md +135 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/entrypoints.svg +53 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/experimental/index.d.ts +17095 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/experimental/index.ts +17050 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/index.d.ts +16306 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/index.ts +16261 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/latest/index.d.ts +16447 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/latest/index.ts +16402 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/oldest/index.d.ts +16306 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/oldest/index.ts +16261 -0
- package/workers/worker_public/node_modules/@cloudflare/workers-types/package.json +11 -0
- package/workers/worker_public/package.json +13 -0
- package/workers/worker_public/prompts/conversational.md +8 -0
- package/workers/worker_public/prompts/enrichment.md +3 -0
- package/workers/worker_public/prompts/faithfulness.md +1 -0
- package/workers/worker_public/prompts/grader.md +5 -0
- package/workers/worker_public/prompts/listwise.md +3 -0
- package/workers/worker_public/prompts/precision.md +1 -0
- package/workers/worker_public/prompts/reflect.md +1 -0
- package/workers/worker_public/prompts/relevancy.md +1 -0
- package/workers/worker_public/prompts/research.md +10 -0
- package/workers/worker_public/prompts/section-summary.md +5 -0
- package/workers/worker_public/prompts/summarize.md +1 -0
- package/workers/worker_public/prompts/system.md +18 -0
- package/workers/worker_public/prompts/understanding.md +17 -0
- package/workers/worker_public/public/app.js +166 -0
- package/workers/worker_public/public/index.html +48 -0
- package/workers/worker_public/public/style.css +147 -0
- package/workers/worker_public/schema.sql +248 -0
- package/workers/worker_public/src/admin.ts +358 -0
- package/workers/worker_public/src/ai.ts +71 -0
- package/workers/worker_public/src/anchors.ts +41 -0
- package/workers/worker_public/src/answercache.ts +72 -0
- package/workers/worker_public/src/ask.ts +1094 -0
- package/workers/worker_public/src/auth.ts +252 -0
- package/workers/worker_public/src/bubble.ts +111 -0
- package/workers/worker_public/src/completion.ts +75 -0
- package/workers/worker_public/src/config.ts +238 -0
- package/workers/worker_public/src/context.ts +238 -0
- package/workers/worker_public/src/conversations.ts +162 -0
- package/workers/worker_public/src/drafts.ts +497 -0
- package/workers/worker_public/src/env.ts +90 -0
- package/workers/worker_public/src/faithfulness.ts +63 -0
- package/workers/worker_public/src/grader.ts +89 -0
- package/workers/worker_public/src/graph.ts +63 -0
- package/workers/worker_public/src/hybrid.ts +77 -0
- package/workers/worker_public/src/index.ts +441 -0
- package/workers/worker_public/src/internal_gateway.ts +41 -0
- package/workers/worker_public/src/lexical.ts +86 -0
- package/workers/worker_public/src/lib/hit.ts +4 -0
- package/workers/worker_public/src/lib/http.ts +83 -0
- package/workers/worker_public/src/lib/router.ts +4 -0
- package/workers/worker_public/src/livedata.ts +334 -0
- package/workers/worker_public/src/memories.ts +81 -0
- package/workers/worker_public/src/modelplane.ts +213 -0
- package/workers/worker_public/src/oidc.ts +333 -0
- package/workers/worker_public/src/pipeline.ts +377 -0
- package/workers/worker_public/src/ports/blobs.ts +7 -0
- package/workers/worker_public/src/ports/cloudflare/adapters.ts +177 -0
- package/workers/worker_public/src/ports/kv.ts +8 -0
- package/workers/worker_public/src/ports/model.ts +28 -0
- package/workers/worker_public/src/ports/runtime.ts +13 -0
- package/workers/worker_public/src/ports/store.ts +20 -0
- package/workers/worker_public/src/ports/vector.ts +26 -0
- package/workers/worker_public/src/profile.gen.ts +101 -0
- package/workers/worker_public/src/profile.ts +16 -0
- package/workers/worker_public/src/projects.ts +108 -0
- package/workers/worker_public/src/prompts.d.ts +6 -0
- package/workers/worker_public/src/quota.ts +54 -0
- package/workers/worker_public/src/reflect.ts +67 -0
- package/workers/worker_public/src/refs.ts +107 -0
- package/workers/worker_public/src/refusal.ts +65 -0
- package/workers/worker_public/src/requestScope.ts +71 -0
- package/workers/worker_public/src/research.ts +126 -0
- package/workers/worker_public/src/search.ts +56 -0
- package/workers/worker_public/src/selfquery.ts +25 -0
- package/workers/worker_public/src/session.ts +4 -0
- package/workers/worker_public/src/share.ts +53 -0
- package/workers/worker_public/src/stages/conceptGraph.ts +68 -0
- package/workers/worker_public/src/stages/conceptSteer.ts +39 -0
- package/workers/worker_public/src/stages/corpusScope.ts +25 -0
- package/workers/worker_public/src/stages/dedup.ts +10 -0
- package/workers/worker_public/src/stages/dense.ts +73 -0
- package/workers/worker_public/src/stages/diversity.ts +33 -0
- package/workers/worker_public/src/stages/editionCover.ts +63 -0
- package/workers/worker_public/src/stages/editionSteer.ts +88 -0
- package/workers/worker_public/src/stages/familyBoost.ts +22 -0
- package/workers/worker_public/src/stages/federate.ts +22 -0
- package/workers/worker_public/src/stages/glossary.ts +65 -0
- package/workers/worker_public/src/stages/graphLane.ts +31 -0
- package/workers/worker_public/src/stages/hyde.ts +29 -0
- package/workers/worker_public/src/stages/index.ts +69 -0
- package/workers/worker_public/src/stages/lexicalUnion.ts +21 -0
- package/workers/worker_public/src/stages/multiQuery.ts +57 -0
- package/workers/worker_public/src/stages/overviewDemote.ts +14 -0
- package/workers/worker_public/src/stages/poolOpen.ts +10 -0
- package/workers/worker_public/src/stages/propagate.ts +15 -0
- package/workers/worker_public/src/stages/rerank.ts +47 -0
- package/workers/worker_public/src/stages/seal.ts +16 -0
- package/workers/worker_public/src/stages/sectionDescent.ts +61 -0
- package/workers/worker_public/src/stages/stdRefNudge.ts +35 -0
- package/workers/worker_public/src/stages/subQuery.ts +42 -0
- package/workers/worker_public/src/stages/termNudge.ts +24 -0
- package/workers/worker_public/src/stages/typedPin.ts +131 -0
- package/workers/worker_public/src/stages/types.ts +112 -0
- package/workers/worker_public/src/stages/windowFloor.ts +23 -0
- package/workers/worker_public/src/structural.ts +171 -0
- package/workers/worker_public/src/tablecontext.ts +41 -0
- package/workers/worker_public/src/understand.ts +72 -0
- package/workers/worker_public/src/understandContract.ts +67 -0
- package/workers/worker_public/src/verdict.ts +255 -0
- package/workers/worker_public/tsconfig.json +18 -0
- package/workers/worker_public/wrangler.toml +104 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Env } from "./env";
|
|
2
|
+
/** Deep-research mode (G10 v1): bounded agentic loop for members —
|
|
3
|
+
* retrieve → sufficiency judge → re-retrieve targeting the gap → answer
|
|
4
|
+
* from the ACCUMULATED evidence. ≤ max_iterations rounds; every
|
|
5
|
+
* iteration's retrieval goes through the same gated pipeline as a
|
|
6
|
+
* normal ask. Workflows (durable, resumable) is the documented upgrade
|
|
7
|
+
* path when runs outgrow a single request. */
|
|
8
|
+
export declare function handleResearch(env: Env, ctx: ExecutionContext, req: Request, session: any): Promise<Response>;
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Background } from "./ports/runtime.ts";
|
|
2
|
+
import { type ApiKey } from "./lib/http";
|
|
3
|
+
import type { Env } from "./env";
|
|
4
|
+
export declare function handleSearch(env: Env, ctx: Background, req: Request, tier: "anon" | "key" | "member", key: ApiKey | null): Promise<Response>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../shared/session.ts";
|
package/dist/share.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Hit } from "./pipeline";
|
|
2
|
+
/** "3.1.2" → [3,1,2]; null for everything else (annex labels, producer
|
|
3
|
+
* UUIDs, overview/family, empty). */
|
|
4
|
+
export declare function parseAnchor(anchor: string | undefined | null): number[] | null;
|
|
5
|
+
/** a is a PROPER ancestor of b ("3.1" ⊳ "3.1.2"). */
|
|
6
|
+
export declare function isAncestorOf(a: number[], b: number[]): boolean;
|
|
7
|
+
/** Document order for dotted numeric anchors ("3" < "3.1" < "3.1.2" < "3.2"). */
|
|
8
|
+
export declare function anchorCompare(a: number[], b: number[]): number;
|
|
9
|
+
/** TreeExpansion-style structural propagation (Eq. 7 of the paper):
|
|
10
|
+
* S(v) = (self + inherited + childAgg)/3, blended into the live score as
|
|
11
|
+
* a spread-scaled adjustment — same idiom as edition steering, so the
|
|
12
|
+
* adjustment can never outrank the cross-encoder's own signal. */
|
|
13
|
+
export declare function structuralPropagation(hits: Hit[]): Hit[];
|
|
14
|
+
/** Position-preserving evidence order (NodeFusion, Algorithm 2): passages
|
|
15
|
+
* of the same publication are fed in document order, publications ordered
|
|
16
|
+
* by their best-ranked member. Document order is the PRODUCER'S ordinal
|
|
17
|
+
* when the metadata carries one (metanorma-document#56) — a sort, never
|
|
18
|
+
* an anchor parse; the anchor compare is the fallback for chunks whose
|
|
19
|
+
* producer doesn't emit ordinals. Structural chunks (overview/family)
|
|
20
|
+
* lead their doc; unnumbered passages follow the numbered ones. */
|
|
21
|
+
export declare function positionOrder(hits: Hit[]): Hit[];
|
|
22
|
+
/** Same-chain near-duplicate collapse: when an ancestor chunk and a
|
|
23
|
+
* descendant chunk of one clause chain carry substantially the same text,
|
|
24
|
+
* the weaker one leaves the window (FABLE keeps the subtree, drops the
|
|
25
|
+
* redundant node). Different-text relatives both stay — a parent clause
|
|
26
|
+
* and a deep sub-clause are usually different content. */
|
|
27
|
+
export declare function ancestorDescendantDedup(hits: Hit[]): Hit[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Schema-aware table context composition (TableRAG-class cell
|
|
2
|
+
* selection) over the producer's typed table payload. Pure — the
|
|
3
|
+
* interface is the test surface. */
|
|
4
|
+
/** Schema-aware table context (TableRAG-class cell selection): the
|
|
5
|
+
* producer payload (metadata.table) carries caption/columns/rows; the
|
|
6
|
+
* consumer composes the model-facing serialization — columns whose
|
|
7
|
+
* labels overlap the query, rows whose cells overlap the query or the
|
|
8
|
+
* selected column labels. Full table stays available for rendering;
|
|
9
|
+
* this only shapes the prompt context, and falls back to the stored
|
|
10
|
+
* text when pruning matches nothing (never worse than baseline). */
|
|
11
|
+
export declare function tableContext(meta: any, query: string): string | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ModelRunner } from "./ports/model.ts";
|
|
2
|
+
import { type QueryUnderstanding } from "./understandContract.ts";
|
|
3
|
+
export type { QueryUnderstanding };
|
|
4
|
+
/** Understand the query with the cheap model. Null = use the regex fallback. */
|
|
5
|
+
export declare function understandQuery(ai: ModelRunner, model: string, query: string, history: Array<{
|
|
6
|
+
role: string;
|
|
7
|
+
content: string;
|
|
8
|
+
}>, entities?: Array<{
|
|
9
|
+
entity: string;
|
|
10
|
+
kind: string;
|
|
11
|
+
}>): Promise<QueryUnderstanding | null>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface QueryUnderstanding {
|
|
2
|
+
/** conversational turn (greeting, identity, small talk) vs knowledge seek */
|
|
3
|
+
intent: "conversational" | "knowledge";
|
|
4
|
+
/** normalized document reference, e.g. "OIML R 76-2" — null when none */
|
|
5
|
+
docidentifier: string | null;
|
|
6
|
+
/** base document number for the Vectorize filter, e.g. "60" */
|
|
7
|
+
doc_number: string | null;
|
|
8
|
+
edition?: string | null;
|
|
9
|
+
language?: string | null;
|
|
10
|
+
/** the question is about a process around publications (certify, apply…) */
|
|
11
|
+
process_intent: boolean;
|
|
12
|
+
/** definition-style question whose subject is `term` */
|
|
13
|
+
term: string | null;
|
|
14
|
+
/** corpus-terminology mapping of everyday wording (drift→creep) */
|
|
15
|
+
defined_terms: string[];
|
|
16
|
+
/** self-contained retrieval query: follow-ups folded with context */
|
|
17
|
+
standalone_query: string;
|
|
18
|
+
complexity: "simple" | "complex";
|
|
19
|
+
query_variants: string[];
|
|
20
|
+
sub_queries: string[];
|
|
21
|
+
hypothetical_answer: string;
|
|
22
|
+
/** plausible next questions (conversational UX), in the user's language */
|
|
23
|
+
follow_ups: string[];
|
|
24
|
+
}
|
|
25
|
+
/** Model output text
|
|
26
|
+
* → QueryUnderstanding (or null). Every silent coercion is pinned by
|
|
27
|
+
* tests/understand.test.ts — change the prompt's JSON shape and the
|
|
28
|
+
* test names what moved. */
|
|
29
|
+
export declare function extractJson(text: string): QueryUnderstanding | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface MachineCheck {
|
|
2
|
+
expression: string;
|
|
3
|
+
symbolic: string;
|
|
4
|
+
values: Record<string, number>;
|
|
5
|
+
result: boolean | null;
|
|
6
|
+
}
|
|
7
|
+
export interface Verdict {
|
|
8
|
+
verdict: "pass" | "fail" | "void";
|
|
9
|
+
on_violation?: string;
|
|
10
|
+
violation_meaning?: string;
|
|
11
|
+
missing: string[];
|
|
12
|
+
checks: MachineCheck[];
|
|
13
|
+
}
|
|
14
|
+
export declare function extractChecks(content: unknown): string[];
|
|
15
|
+
export declare function symbolsIn(checks: string[]): string[];
|
|
16
|
+
export declare function extractParams(query: string, symbols: string[]): Record<string, number>;
|
|
17
|
+
export declare function evaluate(content: unknown, query: string): Verdict | null;
|
|
18
|
+
/** The deterministic note the answer model narrates — never recomputes. */
|
|
19
|
+
export declare function verdictNote(v: Verdict, node: {
|
|
20
|
+
node_id: string;
|
|
21
|
+
clause?: {
|
|
22
|
+
urn?: string;
|
|
23
|
+
} | null;
|
|
24
|
+
}): string;
|
package/docs/API.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# OIML SMART AI — API Reference
|
|
2
|
+
|
|
3
|
+
*The chatbot API of ai.oimlsmart.org: a conversational contract (ask /
|
|
4
|
+
search / sessions / feedback / UI metadata), not a database surface.
|
|
5
|
+
Machine access to the publication registry is via the [MCP
|
|
6
|
+
server](MCP.md). Authorized users: API keys are issued by the service
|
|
7
|
+
operator (see §1.3).*
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Authentication & tiers
|
|
12
|
+
|
|
13
|
+
| Tier | How | Ask quota | Search quota | Model |
|
|
14
|
+
|---|---|---|---|---|
|
|
15
|
+
| **Anonymous** | none (per-IP) | 20/day | 50/day | cost-first lane |
|
|
16
|
+
| **Member** | OIML SMART sign-in (OIDC session cookie) | 300/day | unlimited | member model + ISO/IEC corpus federation |
|
|
17
|
+
| **API key** | `Authorization: Bearer <key>` | per-key (default 2000/day) | per-key | as configured |
|
|
18
|
+
|
|
19
|
+
### 1.1 Anonymous / member
|
|
20
|
+
Just call the API — the session cookie (set by `/auth/login`) upgrades
|
|
21
|
+
requests automatically. CORS is allowed for `*.oimlsmart.org` origins.
|
|
22
|
+
|
|
23
|
+
### 1.2 Sign-in flow (members)
|
|
24
|
+
- `GET /auth/login` → redirects to id.oimlsmart.org (OIDC, PKCE)
|
|
25
|
+
- `GET /auth/callback` → sets the session cookie, returns to the app
|
|
26
|
+
- `GET /auth/me` → `{ authenticated, name, email, roles, tier }`
|
|
27
|
+
- `GET|POST /auth/logout` → clears the session
|
|
28
|
+
|
|
29
|
+
### 1.3 API keys (integrators)
|
|
30
|
+
Keys are `oiml_<hex>`, shown once at creation. Operator issues them:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
curl -X POST https://ai.oimlsmart.org/v1/admin/keys \
|
|
34
|
+
-H "authorization: Bearer $ADMIN_TOKEN" \
|
|
35
|
+
-H 'content-type: application/json' \
|
|
36
|
+
-d '{"name": "partner-integration", "day_limit": 5000}'
|
|
37
|
+
```
|
|
38
|
+
Response includes the plaintext `key` — store it now. List/revoke via
|
|
39
|
+
`GET /v1/admin/keys` (revocation is a D1 update).
|
|
40
|
+
|
|
41
|
+
### 1.4 The bubble bridge (the assistant embedded in estate properties)
|
|
42
|
+
|
|
43
|
+
The shared chrome's assistant panel (site-shell's `AiBubble`) runs on
|
|
44
|
+
OTHER origins (the platform, www, the minisites). The session cookie
|
|
45
|
+
never crosses origins (`SameSite=Lax`, and the estate bans shared-domain
|
|
46
|
+
cookies — the identity guide's SSO doctrine), so the panel carries the
|
|
47
|
+
session as a Bearer token instead:
|
|
48
|
+
|
|
49
|
+
- `GET /auth/login?mode=bubble&origin=<the page's origin>` — the panel
|
|
50
|
+
opens this in a popup. The origin is validated at flow start
|
|
51
|
+
(`https://oimlsmart.org`, `https://*.oimlsmart.org`, or
|
|
52
|
+
`http://localhost[:port]` for dev) and bound to the OIDC state.
|
|
53
|
+
- `GET /auth/callback` — on a bubble flow, sets the cookie as usual AND
|
|
54
|
+
renders a confirm page ("Continue as <name> to <host>?"); only the
|
|
55
|
+
user's explicit click postMessages `{ type: "oimlsmart-ai-session",
|
|
56
|
+
token, name, expiresAt }` to the validated origin (never `*`).
|
|
57
|
+
- The token is the same HMAC-signed payload the cookie carries (7-day
|
|
58
|
+
TTL). The panel sends it as `Authorization: Bearer <token>`; every
|
|
59
|
+
session-gated route (`/auth/me`, `/api/conversations*`,
|
|
60
|
+
`/api/ask|search` member tier) accepts either form. Stateless — there
|
|
61
|
+
is no server-side revocation; sign-out is the panel discarding the
|
|
62
|
+
token.
|
|
63
|
+
|
|
64
|
+
## 2. Asking questions
|
|
65
|
+
|
|
66
|
+
### 2.1 `POST /api/ask` (browser) · `POST /v1/ask` (integrators)
|
|
67
|
+
|
|
68
|
+
Request:
|
|
69
|
+
```jsonc
|
|
70
|
+
{
|
|
71
|
+
"query": "What is the maximum permissible error for class III?", // required, ≤8000 chars
|
|
72
|
+
"lang": "fr", // optional 2-letter hint; answers follow the question's language
|
|
73
|
+
"stream": true, // SSE (default for /api/ask); false → single JSON
|
|
74
|
+
"fresh": false, // true skips BOTH answer caches (exact + semantic) and
|
|
75
|
+
// regenerates from the live corpus; the refreshed answer re-fills the cache
|
|
76
|
+
"prev": "previous user question", // optional, helps follow-up retrieval
|
|
77
|
+
"history": [ {"role":"user","content":"..."}, {"role":"assistant","content":"..."} ], // last ≤20 turns
|
|
78
|
+
"context": { // optional, the declared context (see §2.1.1)
|
|
79
|
+
"kind": "entity", // "page" | "entity" | "document" (absent = none)
|
|
80
|
+
"label": "this certificate R60/2021-A-EX1-26.01", // ≤120 chars, echoed for display
|
|
81
|
+
"route": "/app/standards/r60/certificates/…", // optional, page/entity
|
|
82
|
+
"doc": "urn:oiml:pub:r:60-1:2021", // optional, entity/document: scopes retrieval
|
|
83
|
+
"edition": "2021" // optional 4-digit override
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### 2.1.1 The declared context (the panel's opt-in chips)
|
|
89
|
+
|
|
90
|
+
The estate's assistant panel (site-shell's `AiBubble`) lets the user pin a
|
|
91
|
+
context per message — the page they're on, the entity the page carries, or
|
|
92
|
+
a corpus document. The service applies it honestly:
|
|
93
|
+
|
|
94
|
+
- `doc` accepts the URN provenance form (`urn:oiml:pub:r:60-1:2021`) or the
|
|
95
|
+
plain docidentifier (`OIML R 60-1:2021` / `R 60`). A resolvable document
|
|
96
|
+
scopes retrieval to the publication FAMILY (the same filter a named
|
|
97
|
+
document in the query gets — an entity's clause provenance spans parts).
|
|
98
|
+
The family must exist in the publications registry, else the answer runs
|
|
99
|
+
on the general corpus and says so.
|
|
100
|
+
- A DECLARED scope is a hard seal: the pipeline's soft-steer widenings (the
|
|
101
|
+
sparse-filter union, the lexical union, the sub-query lanes) are cut back
|
|
102
|
+
to the family before generation, because the panel's context line claims
|
|
103
|
+
the grounding — a citation from outside the declared publication would
|
|
104
|
+
make it a lie. (A document named in the question keeps the soft steer —
|
|
105
|
+
the widen covers sparse publications there.) If nothing survives the
|
|
106
|
+
seal, the answer refuses honestly within the scope.
|
|
107
|
+
- A document named **in the question** always wins over the declared chip —
|
|
108
|
+
the context informs, never overrides the user's explicit words. "Named" is
|
|
109
|
+
read from the question's own text (a letter+number mention like "R 76"),
|
|
110
|
+
never from the understanding stage's inference alone: a topic-prior
|
|
111
|
+
extraction is not the user's words and never steals the chip, and a naming
|
|
112
|
+
the text plainly carries wins even when the extraction misses it.
|
|
113
|
+
- The entity's own data is NOT in scope (the "my account" chip's live-data
|
|
114
|
+
delegation, §2.1.2, is the record-level ground); the grounding is the
|
|
115
|
+
governing publication's clauses.
|
|
116
|
+
- A declared context bypasses both answer caches (the answer depends on the
|
|
117
|
+
declaration, not just the query) and is never written into them.
|
|
118
|
+
- Conversational turns (greetings, identity) never ground in a declared
|
|
119
|
+
context — the echo reports `none`.
|
|
120
|
+
|
|
121
|
+
Every ask response — the SSE `citations`/`done` events and the JSON body —
|
|
122
|
+
echoes what was APPLIED, so the panel's context line never invents a
|
|
123
|
+
grounding:
|
|
124
|
+
|
|
125
|
+
```jsonc
|
|
126
|
+
"context_applied": {
|
|
127
|
+
"kind": "entity", // the declared kind, or "none"
|
|
128
|
+
"label": "this certificate R60/2021-A-EX1-26.01",
|
|
129
|
+
"scoped_to": "OIML R 60:2021", // null when the declaration did not scope retrieval
|
|
130
|
+
"note": "question-document-wins" // only when a doc-carrying declaration
|
|
131
|
+
} // did not scope: also "document-not-in-corpus"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### 2.1.2 The "my account" context (TODO.ai-platform/03 — members only)
|
|
135
|
+
|
|
136
|
+
The panel's fourth chip opts the message into the user's OWN live platform
|
|
137
|
+
data — "my account — reads what you can see". The service reads the platform
|
|
138
|
+
EXACTLY as the user, never wider:
|
|
139
|
+
|
|
140
|
+
- The sign-in retains the OP's access token for the session's exchange
|
|
141
|
+
window only (KV keyed by the session token's hash, TTL = the OP token's
|
|
142
|
+
own expiry — never the database, never past the window; sign-out drops
|
|
143
|
+
it deliberately).
|
|
144
|
+
- Each live ask exchanges it at the OP (the RFC 8693 session delegation —
|
|
145
|
+
the identity service's RP guide §9b) for a token scoped
|
|
146
|
+
`<platform>:read`, the account's standing re-judged AT the exchange (a
|
|
147
|
+
role lost mid-session narrows the next answer honestly).
|
|
148
|
+
- The platform reads ride that token; the platform enforces the cones
|
|
149
|
+
exactly as for the user's own browser. The records the answer grounds in
|
|
150
|
+
map 1:1 from the platform's responses — the service can never invent or
|
|
151
|
+
widen one. Org administrators see the exchange + the reads on the audit
|
|
152
|
+
chain (the actor claim names this service), never the conversation.
|
|
153
|
+
- Conversational turns never read the account (a greeting grounds in
|
|
154
|
+
nothing); an account ask whose corpus retrieval is empty still answers
|
|
155
|
+
from the records.
|
|
156
|
+
|
|
157
|
+
The response gains `records` (each `{ store, id, label, url, status?, date?,
|
|
158
|
+
detail? }` — the panel renders the links; the claims about a record name it)
|
|
159
|
+
and the echo carries the live read:
|
|
160
|
+
|
|
161
|
+
```jsonc
|
|
162
|
+
"context_applied": {
|
|
163
|
+
"kind": "account",
|
|
164
|
+
"label": "my account",
|
|
165
|
+
"scoped_to": null, // the account context never scopes the corpus
|
|
166
|
+
"live": { "read_at": "2026-08-31T09:00:00Z", "stores": ["applications"], "records": 3 }
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
When the live read cannot honestly happen the answer runs on the corpus and
|
|
171
|
+
the echo says why: `"note": "sign-in-required"` (no member session),
|
|
172
|
+
`"live-window-expired"` (the sign-in's window lapsed — sign in again to
|
|
173
|
+
refresh), `"live-unavailable"` (the exchange refused or the platform was
|
|
174
|
+
unreachable). Never a silent widening, never an invented record.
|
|
175
|
+
|
|
176
|
+
The deployment wiring: `SMART_PLATFORM_API` (the platform's API base) +
|
|
177
|
+
`SMART_PLATFORM_CLIENT_ID` (its client id at the OP — the delegation's
|
|
178
|
+
scope target). Absent, the chip's ask answers `live-unavailable` honestly.
|
|
179
|
+
|
|
180
|
+
#### 2.1.3 The draft acts (TODO.ai-platform/04 — act with confirmation)
|
|
181
|
+
|
|
182
|
+
The assistant can PREPARE an act; the user commits it in the platform's
|
|
183
|
+
real UI. **The service never writes**: the only credential in play is the
|
|
184
|
+
read-scoped delegation (§2.1.2's exchange), and it feeds nothing but the
|
|
185
|
+
role check. The pilot act is the **application prefill**
|
|
186
|
+
(`application_prefill`); the TL dispatch, the review comment and the
|
|
187
|
+
evaluation summary are named follow-ups on the same contract.
|
|
188
|
+
|
|
189
|
+
A draft ask (the user names the act — "draft / prepare / start / submit an
|
|
190
|
+
R 60 application…") bypasses both answer caches and answers with:
|
|
191
|
+
|
|
192
|
+
- `answer` — a deterministic account of what was drafted, what was
|
|
193
|
+
dropped and why, and the boundary: the draft opens in the real form
|
|
194
|
+
with every field editable, and only the user's own click commits it
|
|
195
|
+
(the service never claims a performed act).
|
|
196
|
+
- `draft` — the wire shape below (ABSENT on a refusal).
|
|
197
|
+
- `citations` — the resolved Recommendation the act anchors on.
|
|
198
|
+
|
|
199
|
+
```jsonc
|
|
200
|
+
"draft": {
|
|
201
|
+
"kind": "draft",
|
|
202
|
+
"act": "application_prefill",
|
|
203
|
+
"version": 1,
|
|
204
|
+
"title": "New OIML R 60:2021 application",
|
|
205
|
+
"prepared_at": "2026-08-31T10:00:00Z",
|
|
206
|
+
"requires_confirmation": true, // ALWAYS — the draft is an input, never a channel
|
|
207
|
+
"fields": {
|
|
208
|
+
"standard_doc": "urn:oiml:pub:r:60:2021", // the estate URN, resolved against the corpus registry
|
|
209
|
+
"standard_label": "OIML R 60:2021",
|
|
210
|
+
"family_designation": "LC series", // only fields the user stated
|
|
211
|
+
"model_designation": "LC-500",
|
|
212
|
+
"description": "…",
|
|
213
|
+
"samples": [{ "serial": "SN-0042", "condition": "NEW" }],
|
|
214
|
+
"scheme": "A" // or "B"
|
|
215
|
+
},
|
|
216
|
+
"dropped": [ // the never-invents account
|
|
217
|
+
{ "field": "family_designation", "value": "Phantom-9", "reason": "not stated in your own words" }
|
|
218
|
+
],
|
|
219
|
+
"notes": ["The technical parameters stay with you: the form derives what the model declares…"]
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The honest rules, all eval-gated (the golden suite's `draft-*` legs):
|
|
224
|
+
|
|
225
|
+
- **Never-invents**: the extraction (an LLM pass) only PROPOSES fields;
|
|
226
|
+
every drafted value must trace — through its own source span — to the
|
|
227
|
+
user's own messages, or it lands in `dropped` and the answer names it.
|
|
228
|
+
The instrument model's derivations never ride the draft: the platform's
|
|
229
|
+
form derives them on open, the user confirms each one.
|
|
230
|
+
- **The refusals speak the platform's role vocabulary** (read from the
|
|
231
|
+
exchanged token's `service_roles`, re-judged live at the exchange):
|
|
232
|
+
the anonymous visitor is asked to sign in; a role that cannot perform
|
|
233
|
+
the act (the test-lab operator, the issuing-authority officer, the
|
|
234
|
+
read-only viewer) is told what the account IS and that the act belongs
|
|
235
|
+
to the applicant — no draft. Fail-closed: a role the pilot does not
|
|
236
|
+
recognize refuses. An unresolvable Recommendation (never named by the
|
|
237
|
+
user, or not in the corpus) refuses — the act anchors on a real
|
|
238
|
+
document.
|
|
239
|
+
- **The never-writes must-not**: a crafted prompt ("submit it now with
|
|
240
|
+
my token") yields at most a draft with `requires_confirmation: true` —
|
|
241
|
+
the response NEVER carries a performed-act marker. The platform's
|
|
242
|
+
bearer cone refuses the delegated write class outright either way;
|
|
243
|
+
the commit path is the platform's own form, its own validation, its
|
|
244
|
+
own audit (which marks the act AI-prepared).
|
|
245
|
+
|
|
246
|
+
**SSE**: the `draft` rides the first (`citations`) frame beside
|
|
247
|
+
`context_applied`; `token` frames carry the answer; `done` as usual.
|
|
248
|
+
The draft is ephemeral — the conversations API never persists it (a
|
|
249
|
+
resumed session keeps the honest context line, not a stale draft).
|
|
250
|
+
|
|
251
|
+
#### 2.1.4 The model plane (TODO.ai-platform/05 — model-native grounding)
|
|
252
|
+
|
|
253
|
+
The assistant grounds in the SMART Recommendation MODELS, not only the
|
|
254
|
+
prose corpus. The packages' machine content — the requirements'
|
|
255
|
+
constraints (the machine limits the platform's verdict engine evaluates),
|
|
256
|
+
the applicability rules, the acceptance criteria, the conformance tests,
|
|
257
|
+
the term definitions — indexes **alongside** the prose (Vectorize corpus
|
|
258
|
+
`smart-model` + the D1 `model_nodes` store + the FTS lane). The index
|
|
259
|
+
DERIVES from the primmel packages, the models' single source of truth:
|
|
260
|
+
the smart repo's `derive-model-plane.ts` projects the packages into
|
|
261
|
+
committed bundles (`browser/public/data/model-plane/*.json`, byte-clean-
|
|
262
|
+
guarded by its SSOT gate) and `python -m ingest.cli model-plane` consumes
|
|
263
|
+
them from the sibling smart checkout (`SMART_REPO`). **The freshness is
|
|
264
|
+
gated**: every bundle carries the package's `source_hash`; the committed
|
|
265
|
+
pins (`ingest/model_plane_pins.json`) record what the index derived from,
|
|
266
|
+
and `python -m ingest.cli model-plane --check` (CI: the ingest job) fails
|
|
267
|
+
when a package moved — a package change re-indexes.
|
|
268
|
+
|
|
269
|
+
**The model-aware chips.** "This requirement" on a model surface (the
|
|
270
|
+
platform's requirement / conformance-test / term pages) declares an
|
|
271
|
+
entity context whose label leads with the canonical node id
|
|
272
|
+
(`/req/metrological/mpe — Maximum permissible errors…`). The service
|
|
273
|
+
binds the node EXACTLY (the strict id grammar — never a fuzzy match):
|
|
274
|
+
the declared label's id wins, then a node id the question names; the
|
|
275
|
+
standard comes from the declared or question-named publication only (an
|
|
276
|
+
LLM inference never narrows the bind), and a scope-less id binds only
|
|
277
|
+
when unambiguous across the indexed standards — an ambiguous or
|
|
278
|
+
unindexed id binds NOTHING, honestly. A bound node grounds the answer in
|
|
279
|
+
the node itself: its constraint (quoted verbatim), its applicability,
|
|
280
|
+
its acceptance, its provenance, its tests/preconditions ride the prompt
|
|
281
|
+
as a structured block, the citations lead with the model node
|
|
282
|
+
(`corpus: "smart-model"`), and the echo names the grounding:
|
|
283
|
+
|
|
284
|
+
```jsonc
|
|
285
|
+
"context_applied": {
|
|
286
|
+
"kind": "entity",
|
|
287
|
+
"label": "this requirement /req/metrological/mpe — Maximum permissible errors on type evaluation",
|
|
288
|
+
"scoped_to": "OIML R 60:2021",
|
|
289
|
+
"model": {
|
|
290
|
+
"node_id": "/req/metrological/mpe",
|
|
291
|
+
"kind": "requirement",
|
|
292
|
+
"standard": "oiml-r60",
|
|
293
|
+
"clause": "urn:oiml:pub:r:60-1:2021#clause-5.3.2"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**The explained verdict.** The applicability/evaluation engines' verdicts
|
|
299
|
+
answer in plain language with the constraint + the clause + the user's
|
|
300
|
+
value ("your class C instrument fails 5.3.2 because the MPE for class C is
|
|
301
|
+
±…, your declared value is …"). The verdict EXPLANATION is the platform's
|
|
302
|
+
computation — the engine's own trace seam (the smart repo's
|
|
303
|
+
`engine/verdict-explanation.ts`, proven on its golden cases), never a
|
|
304
|
+
constraint this service invents or recomputes. The model plane grounds
|
|
305
|
+
the static half (the requirement's machine limit + its clause + its
|
|
306
|
+
acceptance); a live verdict's trace is the platform's own computation.
|
|
307
|
+
|
|
308
|
+
**The honesty (the clause-drift doctrine's posture).** Where the model
|
|
309
|
+
and the prose disagree, the answer says so and cites both: a model node
|
|
310
|
+
that DECLARES a source discrepancy (the packages' `source_discrepancy`
|
|
311
|
+
annotation — e.g. R 60-1, 5.6.3.1's C_Hmax ≤ 1 v vs R 60-3, 2.1.7's
|
|
312
|
+
C_Hmax ≤ MPE) carries it into the grounding block verbatim and the answer
|
|
313
|
+
must surface it; and every retrieved model-plane passage rides the corpus
|
|
314
|
+
note — where a model passage and a prose passage disagree (a different
|
|
315
|
+
edition's prose included), say so explicitly and cite both.
|
|
316
|
+
|
|
317
|
+
The eval legs (the golden suite's `model-*` cases): the explained-
|
|
318
|
+
verdict shape (the machine limit + the clause + the value in the answer),
|
|
319
|
+
the model-aware chip binding (the echo above), the disagreement posture,
|
|
320
|
+
and the must-not (an unbindable declaration carries NO model echo —
|
|
321
|
+
never an invented grounding).
|
|
322
|
+
|
|
323
|
+
**SSE protocol** (`text/event-stream`, each line `data: {json}`):
|
|
324
|
+
1. `{"type":"citations","citations":[...],"quota":{"used":n,"limit":m}}` — arrives FIRST so chips render while the answer streams
|
|
325
|
+
2. `{"type":"token","v":"…"}` — repeated, in order
|
|
326
|
+
3. `{"type":"done","model":"…","query_hash":"…","follow_ups":["…","…"],"similar":false}`
|
|
327
|
+
|
|
328
|
+
`similar: true` means the answer came from the semantic cache (a
|
|
329
|
+
near-identical recent question). `follow_ups` are suggested next
|
|
330
|
+
questions (present when understanding produced them).
|
|
331
|
+
|
|
332
|
+
**Citation object** (also the shape in the `done`/JSON responses):
|
|
333
|
+
```jsonc
|
|
334
|
+
{
|
|
335
|
+
"doc_id": "clean:r060/1", "docidentifier": "OIML R 60-1",
|
|
336
|
+
"edition": "2021", "clause_anchor": "4.1.2", "clause_title": "…",
|
|
337
|
+
"snippet": "…", "status": "in-force", // or superseded/withdrawn
|
|
338
|
+
"superseded_by": "OIML R 60-1:2021", // when applicable
|
|
339
|
+
"url": "https://…" // deep link to the rendering when available
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Answer conventions:**
|
|
344
|
+
- Claims cite inline as `[OIML R 60-1:2021 §4.1.2]`
|
|
345
|
+
- Normative values carry quote anchors: `[OIML R 76:2004 §3.2: "the maximum permissible error shall not exceed 0.5e"]` — the quoted phrase is verbatim from the cited passage (mechanically checkable)
|
|
346
|
+
- Out-of-corpus questions return exactly: `I don't have information on this in the indexed OIML publications.` followed by a short redirect
|
|
347
|
+
- Conversational turns (greetings, identity, capability) are answered directly without citations; off-topic SUBJECT questions are still treated as knowledge questions
|
|
348
|
+
|
|
349
|
+
**Non-stream response** (`stream:false`): the same fields as one JSON
|
|
350
|
+
object: `{ answer, citations, model, query_hash, follow_ups, similar?, quota?, cached? }`
|
|
351
|
+
plus `context`: the passages the answer was actually built from
|
|
352
|
+
(`[{doc_id, clause_anchor, text}]`, response-only, never cached) — grounding
|
|
353
|
+
transparency for integrators and the eval battery.
|
|
354
|
+
|
|
355
|
+
### 2.2 Behavior guarantees
|
|
356
|
+
- Refusals are never cached or served from the semantic cache
|
|
357
|
+
- Conversational/contextual turns are never served from caches
|
|
358
|
+
- Both answer caches are namespaced by INDEX_VERSION (bumped on deploy)
|
|
359
|
+
and a corpus-generation stamp (KV `sys:corpus_gen`, bumped by
|
|
360
|
+
`scripts/invalidate_answer_cache.py` after corpus surgery) — a corpus
|
|
361
|
+
change makes old-generation entries miss, never serve
|
|
362
|
+
- Edition awareness: when the question names a publication, answers are
|
|
363
|
+
steered to the ACTIVE edition (derived publication registry)
|
|
364
|
+
- ISO/IEC corpus: federated for signed-in members only; leakage is
|
|
365
|
+
structurally impossible for anonymous/key tiers
|
|
366
|
+
|
|
367
|
+
### 2.3 `POST /api/research` (members only)
|
|
368
|
+
```jsonc
|
|
369
|
+
{ "query": "trace the creep and return requirements across R 60-1 and R 76-1", "max_iterations": 3 }
|
|
370
|
+
```
|
|
371
|
+
Bounded agentic loop (≤3 retrieve→judge→refine iterations) over the same gated retrieval; returns `{ answer, citations, model, blocks?, research: { iterations, passages, elapsed_ms } }`. Not streamed; expect up to ~90s.
|
|
372
|
+
|
|
373
|
+
**Blocks (answer contract v2):** answers may reference typed MKO units as `[[u:<id>]]` tokens inside `text`; the response carries `blocks: [{ unit_id, type: table|formula|figure|term, docidentifier, edition?, payload }]` — producer-validated payloads (MN 116), never model-retyped data. Invalid references are dropped server-side before rendering.
|
|
374
|
+
|
|
375
|
+
## 3. Retrieval-only
|
|
376
|
+
|
|
377
|
+
### `POST /api/search` · `POST /v1/search`
|
|
378
|
+
```jsonc
|
|
379
|
+
{ "query": "maximum permissible error", "top_k": 5 } // top_k ≤ 10
|
|
380
|
+
```
|
|
381
|
+
Response:
|
|
382
|
+
```jsonc
|
|
383
|
+
{ "results": [ { "doc_id","docidentifier","edition","language",
|
|
384
|
+
"clause_anchor","clause_title","status","superseded_by",
|
|
385
|
+
"text","score" } ],
|
|
386
|
+
"filters": { …applied metadata filters… }, "quota": { … } }
|
|
387
|
+
```
|
|
388
|
+
No generation cost — embedding + hybrid retrieval only.
|
|
389
|
+
|
|
390
|
+
## 4. UI metadata
|
|
391
|
+
|
|
392
|
+
### `GET /api/datasets`
|
|
393
|
+
```jsonc
|
|
394
|
+
{ "datasets": [ { "id","label","description","enabled",
|
|
395
|
+
"requires": "an OIML SMART account" } ], // locked corpora
|
|
396
|
+
"suggestions": ["What is R 60?", …] } // starter questions
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## 5. Conversations (members)
|
|
400
|
+
|
|
401
|
+
| Route | Method | Purpose |
|
|
402
|
+
|---|---|---|
|
|
403
|
+
| `/api/conversations` | GET | list synced conversations |
|
|
404
|
+
| `/api/conversations` | POST | create |
|
|
405
|
+
| `/api/conversations/{id}` | GET | one conversation |
|
|
406
|
+
| `/api/conversations/{id}` | PATCH | rename |
|
|
407
|
+
| `/api/conversations/{id}` | DELETE | remove |
|
|
408
|
+
| `/api/conversations/{id}/messages` | POST | append a message |
|
|
409
|
+
|
|
410
|
+
### Sharing (members)
|
|
411
|
+
- `POST /api/conversations/{id}/share` → `{ "url": "https://…/s/<slug>" }` (10 shares/day)
|
|
412
|
+
- `GET /api/shared/{slug}` → read-only rendered conversation (public link)
|
|
413
|
+
|
|
414
|
+
## 6. Feedback
|
|
415
|
+
|
|
416
|
+
`POST /api/feedback` `{ "query_hash": "<64-hex>", "rating": 1 | -1 }` —
|
|
417
|
+
thumbs up/down on an answer; logged to D1 for eval.
|
|
418
|
+
|
|
419
|
+
## 7. Operations (operator token)
|
|
420
|
+
|
|
421
|
+
| Route | Purpose |
|
|
422
|
+
|---|---|
|
|
423
|
+
| `GET /health` | liveness + index version |
|
|
424
|
+
| `POST /v1/admin/keys` / `GET` | API key issue/list |
|
|
425
|
+
| `GET /v1/admin/stats` | 7-day query/spend by model, feedback ratios |
|
|
426
|
+
| `POST /admin/enrich` | contextual-enrichment batches (Bearer ADMIN_TOKEN; internal) |
|
|
427
|
+
| `POST /admin/judge` | RAGAS-style scoring (question/answer/passages; internal eval) |
|
|
428
|
+
|
|
429
|
+
## 8. Errors
|
|
430
|
+
|
|
431
|
+
| HTTP | code | meaning |
|
|
432
|
+
|---|---|---|
|
|
433
|
+
| 400 | `invalid_input` | bad query/shape |
|
|
434
|
+
| 401 | `unauthorized` | bad key / no session where required |
|
|
435
|
+
| 429 | `quota_exceeded` | daily limit reached |
|
|
436
|
+
| 503 | `generation_disabled` / `retrieval_unavailable` | kill switch / transient — retry |
|
|
437
|
+
| 502 | `generation_failed` | model unavailable — retry |
|
|
438
|
+
|
|
439
|
+
## 9. Minimal client
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
curl -N https://ai.oimlsmart.org/v1/ask \
|
|
443
|
+
-H "authorization: Bearer $KEY" -H 'content-type: application/json' \
|
|
444
|
+
-d '{"query":"What is OIML R 60?","stream":true}'
|
|
445
|
+
```
|
|
446
|
+
```js
|
|
447
|
+
const res = await fetch("/api/ask", {method:"POST",
|
|
448
|
+
headers:{"content-type":"application/json"},
|
|
449
|
+
body: JSON.stringify({query, stream:true})});
|
|
450
|
+
for await (const chunk of res.body) { /* parse data: lines */ }
|
|
451
|
+
```
|