@konneal/engine 0.2.38 → 0.2.40
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/dist/{ask-VXV2BFNC.js → ask-QHODBUU2.js} +2 -2
- package/dist/{chunk-CNFQNCWA.js → chunk-5SLSRFOG.js} +4 -3
- package/dist/{chunk-GDQOFP6B.js → chunk-AKAFLMIB.js} +1 -1
- package/dist/{chunk-6GAYBMUM.js → chunk-N2UOENDY.js} +31 -8
- package/dist/env.d.ts +4 -0
- package/dist/livedata.d.ts +11 -3
- package/dist/quality.d.ts +12 -1
- package/dist/{search-HBE47CK6.js → search-QZDQVB57.js} +2 -2
- package/dist/worker_public/src/index.js +4 -4
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +3 -3
- package/workers/worker_public/src/env.ts +4 -0
- package/workers/worker_public/src/livedata.ts +27 -7
- package/workers/worker_public/src/quality.ts +25 -4
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
contextNote,
|
|
10
10
|
editionNote,
|
|
11
11
|
embed,
|
|
12
|
+
experimentalSourceLabels,
|
|
12
13
|
generateOnce,
|
|
13
14
|
graphExpand,
|
|
14
15
|
identityNote,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
syntheticUnderstanding,
|
|
35
36
|
telemetry,
|
|
36
37
|
understandQuery
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-N2UOENDY.js";
|
|
38
39
|
import {
|
|
39
40
|
corsHeaders,
|
|
40
41
|
err,
|
|
@@ -2475,7 +2476,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2475
2476
|
`));
|
|
2476
2477
|
send({ type: "read", read: readAs() });
|
|
2477
2478
|
const sourceQuality = answerQuality(cites.map((c) => c.quality));
|
|
2478
|
-
send({ type: "citations", citations: cites, context_applied: ctxApplied, ...sourceQuality ? { source_quality: sourceQuality, quality_note: qualityNote(sourceQuality) } : {}, ...liveRecords ? { records: liveRecords } : {}, quota });
|
|
2479
|
+
send({ type: "citations", citations: cites, context_applied: ctxApplied, ...sourceQuality ? { source_quality: sourceQuality, quality_note: qualityNote(sourceQuality), ...sourceQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(cites) } : {} } : {}, ...liveRecords ? { records: liveRecords } : {}, quota });
|
|
2479
2480
|
let full = "";
|
|
2480
2481
|
try {
|
|
2481
2482
|
for await (const tok of sseTokens(stream)) {
|
|
@@ -2609,7 +2610,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2609
2610
|
}
|
|
2610
2611
|
completionBlocks.push(...await completeFigures(env.DB, answer, [...c2ns.blocks, ...completionBlocks], used));
|
|
2611
2612
|
const jsonQuality = answerQuality(finalCites.map((c) => c.quality));
|
|
2612
|
-
const out = { answer, citations: finalCites, ...jsonQuality ? { source_quality: jsonQuality, quality_note: qualityNote(jsonQuality) } : {}, model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...aggregationBlock ? [aggregationBlock] : [], ...completionBlocks], context_applied: ctxApplied, ...liveRecords ? { records: liveRecords } : {} };
|
|
2613
|
+
const out = { answer, citations: finalCites, ...jsonQuality ? { source_quality: jsonQuality, quality_note: qualityNote(jsonQuality), ...jsonQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(finalCites) } : {} } : {}, model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...aggregationBlock ? [aggregationBlock] : [], ...completionBlocks], context_applied: ctxApplied, ...liveRecords ? { records: liveRecords } : {} };
|
|
2613
2614
|
const cacheable = !contextual && !declaredCtx && !answer.includes(refusalAnswer()) && finalAnchors.violations.length === 0;
|
|
2614
2615
|
if (cacheable) {
|
|
2615
2616
|
const warmVec = await warmEmbed ?? null;
|
|
@@ -77,9 +77,21 @@ function answerQuality(qualities) {
|
|
|
77
77
|
return worst;
|
|
78
78
|
}
|
|
79
79
|
function qualityNote(q) {
|
|
80
|
-
if (q === "verified") return "
|
|
81
|
-
if (q === "curated") return "
|
|
82
|
-
return "Partly grounded in experimental
|
|
80
|
+
if (q === "verified") return "High confidence: this publication's requirements ride a machine-checkable data model.";
|
|
81
|
+
if (q === "curated") return "Established sources: Metanorma-edited documents, chunked at clause boundaries.";
|
|
82
|
+
return "WARNING: Partly grounded in experimental data source that was derived from OCR content. Please verify content against official publications.";
|
|
83
|
+
}
|
|
84
|
+
function experimentalSourceLabels(cites, cap = 6) {
|
|
85
|
+
const labels = [];
|
|
86
|
+
for (const c of cites) {
|
|
87
|
+
if (c.quality !== "ocr") continue;
|
|
88
|
+
const id = String(c.docidentifier || c.doc_id || "source");
|
|
89
|
+
const anchor = String(c.clause_anchor ?? "");
|
|
90
|
+
const garbage = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(anchor) || anchor.startsWith("_") && anchor.length > 12;
|
|
91
|
+
labels.push(garbage || !anchor || anchor === "overview" ? id : `${id} \xA7${anchor}`);
|
|
92
|
+
if (labels.length >= cap) break;
|
|
93
|
+
}
|
|
94
|
+
return labels;
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
// workers/worker_public/src/lexical.ts
|
|
@@ -23836,13 +23848,23 @@ function opMemberCanWrite(member) {
|
|
|
23836
23848
|
if (member.via !== "op-token") return true;
|
|
23837
23849
|
return /\bwrite\b/.test(member.scope ?? "");
|
|
23838
23850
|
}
|
|
23839
|
-
function opMemberFromIntrospection(answer, ids) {
|
|
23851
|
+
function opMemberFromIntrospection(answer, ids, patServices = []) {
|
|
23840
23852
|
if (!answer?.active) return null;
|
|
23841
|
-
const clientId = String(answer.client_id ?? "");
|
|
23842
|
-
if (!ids.includes(clientId)) return null;
|
|
23843
23853
|
const sub = String(answer.sub ?? "");
|
|
23844
23854
|
if (!sub) return null;
|
|
23845
|
-
|
|
23855
|
+
const clientId = String(answer.client_id ?? "");
|
|
23856
|
+
if (clientId) {
|
|
23857
|
+
if (!ids.includes(clientId)) return null;
|
|
23858
|
+
return { sub, scope: String(answer.scope ?? ""), via: "op-token" };
|
|
23859
|
+
}
|
|
23860
|
+
if (!patServices.length) return null;
|
|
23861
|
+
const scope = String(answer.scope ?? "");
|
|
23862
|
+
const services = scope.split(/\s+/).filter(Boolean).map((s8) => s8.split(":")[0]);
|
|
23863
|
+
if (!services.some((s8) => patServices.includes(s8))) return null;
|
|
23864
|
+
return { sub, scope, via: "op-token" };
|
|
23865
|
+
}
|
|
23866
|
+
function patServiceIds(env) {
|
|
23867
|
+
return String(env.OIDC_PAT_SERVICES ?? "").split(/[\s,]+/).filter(Boolean);
|
|
23846
23868
|
}
|
|
23847
23869
|
async function opTokenMember(env, cfg, req) {
|
|
23848
23870
|
const m = /^Bearer\s+(.+)$/i.exec((req.headers.get("Authorization") ?? "").trim());
|
|
@@ -23872,7 +23894,7 @@ async function opTokenMember(env, cfg, req) {
|
|
|
23872
23894
|
} catch {
|
|
23873
23895
|
}
|
|
23874
23896
|
}
|
|
23875
|
-
return opMemberFromIntrospection(answer, ids);
|
|
23897
|
+
return opMemberFromIntrospection(answer, ids, patServiceIds(env));
|
|
23876
23898
|
}
|
|
23877
23899
|
|
|
23878
23900
|
// workers/worker_public/src/oidc.ts
|
|
@@ -24584,6 +24606,7 @@ export {
|
|
|
24584
24606
|
generateOnce,
|
|
24585
24607
|
answerQuality,
|
|
24586
24608
|
qualityNote,
|
|
24609
|
+
experimentalSourceLabels,
|
|
24587
24610
|
ftsMatchQuery,
|
|
24588
24611
|
refCodec,
|
|
24589
24612
|
NO_CONTEXT,
|
package/dist/env.d.ts
CHANGED
|
@@ -36,6 +36,10 @@ export interface Env {
|
|
|
36
36
|
* deployment admits as member-read credentials via introspection.
|
|
37
37
|
* Empty/absent = the tier is off. */
|
|
38
38
|
OIDC_DEVICE_CLIENT_IDS?: string;
|
|
39
|
+
/** the services whose OP-minted personal access tokens (the PAT form
|
|
40
|
+
* of introspection — no client_id on the wire) this deployment admits
|
|
41
|
+
* as member-read credentials. Empty/absent = PATs stay anonymous. */
|
|
42
|
+
OIDC_PAT_SERVICES?: string;
|
|
39
43
|
SESSION_SECRET?: string;
|
|
40
44
|
/** TODO.ai-platform/03 — the "my account" live-data delegation: the
|
|
41
45
|
* platform instance's API base + its client id at the OP (the
|
package/dist/livedata.d.ts
CHANGED
|
@@ -111,9 +111,17 @@ export declare function opMemberCanWrite(member: {
|
|
|
111
111
|
scope?: string;
|
|
112
112
|
} | null): boolean;
|
|
113
113
|
/** The introspection answer → the member credential, judged against the
|
|
114
|
-
* deployment's
|
|
115
|
-
*
|
|
116
|
-
|
|
114
|
+
* deployment's two allowlists. The OP answers client-bearing tokens
|
|
115
|
+
* WITH client_id (the access-token table, the machine JWTs) and its
|
|
116
|
+
* PAT form WITHOUT one — a PAT's identity on the wire is its granted
|
|
117
|
+
* SERVICES (the PAT grammar's whole vocabulary). So the deployment
|
|
118
|
+
* names device-grant clients (OIDC_DEVICE_CLIENT_IDS) and, separately,
|
|
119
|
+
* the services whose personal access tokens it admits
|
|
120
|
+
* (OIDC_PAT_SERVICES). Both lists empty = the tier stays off; a token
|
|
121
|
+
* matching neither list resolves to null — never a widened guess. */
|
|
122
|
+
export declare function opMemberFromIntrospection(answer: any, ids: string[], patServices?: string[]): OpMember | null;
|
|
123
|
+
/** The PAT-services allowlist (see opMemberFromIntrospection). */
|
|
124
|
+
export declare function patServiceIds(env: any): string[];
|
|
117
125
|
/** The Bearer read + introspection, KV-cached 45s keyed by the token's
|
|
118
126
|
* hash (an ask burst costs one introspection; a deactivation lands
|
|
119
127
|
* within a minute). The cached object is the OP's RAW answer — the
|
package/dist/quality.d.ts
CHANGED
|
@@ -2,5 +2,16 @@ import type { ChunkMeta } from "../../shared/chunk.ts";
|
|
|
2
2
|
export type SourceQuality = "verified" | "curated" | "ocr";
|
|
3
3
|
export declare function hitQuality(m: ChunkMeta): SourceQuality;
|
|
4
4
|
export declare function answerQuality(qualities: (SourceQuality | undefined)[]): SourceQuality | null;
|
|
5
|
-
/** The one-line posture the interface renders under the answer.
|
|
5
|
+
/** The one-line posture the interface renders under the answer. The
|
|
6
|
+
* OCR rung is the owner's 2026-09-26 wording, verbatim. */
|
|
6
7
|
export declare function qualityNote(q: SourceQuality): string;
|
|
8
|
+
/** The citations standing on the OCR rung, named for the reader — the
|
|
9
|
+
* confidence line says WHICH sources are experimental, not just that
|
|
10
|
+
* some are. Producer-UUID anchors say nothing and are dropped; a cap
|
|
11
|
+
* keeps the line readable on citation-heavy answers. */
|
|
12
|
+
export declare function experimentalSourceLabels(cites: {
|
|
13
|
+
quality?: string;
|
|
14
|
+
docidentifier?: string;
|
|
15
|
+
doc_id?: string;
|
|
16
|
+
clause_anchor?: string;
|
|
17
|
+
}[], cap?: number): string[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleSearch
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-AKAFLMIB.js";
|
|
4
4
|
import {
|
|
5
5
|
bindModelNode,
|
|
6
6
|
checkQuoteAnchors,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
modelGroundingBlock,
|
|
10
10
|
scoreJudge,
|
|
11
11
|
standardForDocNumber
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-5SLSRFOG.js";
|
|
13
13
|
import {
|
|
14
14
|
buildMessages,
|
|
15
15
|
citations,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
sessionFrom,
|
|
36
36
|
telemetry,
|
|
37
37
|
understandQuery
|
|
38
|
-
} from "../../chunk-
|
|
38
|
+
} from "../../chunk-N2UOENDY.js";
|
|
39
39
|
import {
|
|
40
40
|
authenticate,
|
|
41
41
|
corsHeaders,
|
|
@@ -999,7 +999,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
999
999
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
1000
1000
|
body: JSON.stringify({ ...args, stream: false })
|
|
1001
1001
|
});
|
|
1002
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
1002
|
+
const res = name === "ask" ? await (await import("../../ask-QHODBUU2.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-QZDQVB57.js")).handleSearch(env, ctx, inner, tier, key);
|
|
1003
1003
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
1004
1004
|
});
|
|
1005
1005
|
if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
|
package/package.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"playwright": "^1.62.1",
|
|
28
28
|
"yaml": "^2.9.1"
|
|
29
29
|
},
|
|
30
|
-
"version": "0.2.
|
|
30
|
+
"version": "0.2.40",
|
|
31
31
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
32
32
|
"license": "BSD-3-Clause",
|
|
33
33
|
"type": "module",
|
|
@@ -25,7 +25,7 @@ import { evaluate as machineEvaluate, verdictNote } from "./verdict";
|
|
|
25
25
|
import { evaluateConditionSets, quantitiesIn, type ConditionVerdict } from "./conditions";
|
|
26
26
|
import { evaluateAggregation, type AggregationVerdict } from "./aggregation";
|
|
27
27
|
import { matchLicensedTopic, boundaryNoteText } from "./boundary";
|
|
28
|
-
import { answerQuality, qualityNote } from "./quality";
|
|
28
|
+
import { answerQuality, experimentalSourceLabels, qualityNote } from "./quality";
|
|
29
29
|
import { detectDraftIntent, prepareDraft } from "./drafts";
|
|
30
30
|
import { detectApiCallIntent, prepareApiCall } from "./apicalls";
|
|
31
31
|
import { memoryNote } from "./memories";
|
|
@@ -1147,7 +1147,7 @@ async function handleAsk(
|
|
|
1147
1147
|
// retrieval, before a single token of the answer
|
|
1148
1148
|
send({ type: "read", read: readAs() });
|
|
1149
1149
|
const sourceQuality = answerQuality(cites.map((c: any) => c.quality));
|
|
1150
|
-
send({ type: "citations", citations: cites, context_applied: ctxApplied, ...(sourceQuality ? { source_quality: sourceQuality, quality_note: qualityNote(sourceQuality) } : {}), ...(liveRecords ? { records: liveRecords } : {}), quota, });
|
|
1150
|
+
send({ type: "citations", citations: cites, context_applied: ctxApplied, ...(sourceQuality ? { source_quality: sourceQuality, quality_note: qualityNote(sourceQuality), ...(sourceQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(cites) } : {}) } : {}), ...(liveRecords ? { records: liveRecords } : {}), quota, });
|
|
1151
1151
|
let full = "";
|
|
1152
1152
|
try {
|
|
1153
1153
|
for await (const tok of sseTokens(stream)) {
|
|
@@ -1342,7 +1342,7 @@ async function handleAsk(
|
|
|
1342
1342
|
completionBlocks.push(...(await completeFigures(env.DB, answer, [...c2ns.blocks, ...completionBlocks], used)));
|
|
1343
1343
|
|
|
1344
1344
|
const jsonQuality = answerQuality(finalCites.map((c: any) => c.quality));
|
|
1345
|
-
const out = { answer, citations: finalCites, ...(jsonQuality ? { source_quality: jsonQuality, quality_note: qualityNote(jsonQuality) } : {}), model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : []), ...completionBlocks], context_applied: ctxApplied, ...(liveRecords ? { records: liveRecords } : {}) };
|
|
1345
|
+
const out = { answer, citations: finalCites, ...(jsonQuality ? { source_quality: jsonQuality, quality_note: qualityNote(jsonQuality), ...(jsonQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(finalCites) } : {}) } : {}), model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...(aggregationBlock ? [aggregationBlock] : []), ...completionBlocks], context_applied: ctxApplied, ...(liveRecords ? { records: liveRecords } : {}) };
|
|
1346
1346
|
const cacheable = !contextual && !declaredCtx && !answer.includes(refusalAnswer()) && finalAnchors.violations.length === 0;
|
|
1347
1347
|
if (cacheable) {
|
|
1348
1348
|
const warmVec = (await warmEmbed) ?? null;
|
|
@@ -38,6 +38,10 @@ export interface Env {
|
|
|
38
38
|
* deployment admits as member-read credentials via introspection.
|
|
39
39
|
* Empty/absent = the tier is off. */
|
|
40
40
|
OIDC_DEVICE_CLIENT_IDS?: string;
|
|
41
|
+
/** the services whose OP-minted personal access tokens (the PAT form
|
|
42
|
+
* of introspection — no client_id on the wire) this deployment admits
|
|
43
|
+
* as member-read credentials. Empty/absent = PATs stay anonymous. */
|
|
44
|
+
OIDC_PAT_SERVICES?: string;
|
|
41
45
|
SESSION_SECRET?: string;
|
|
42
46
|
/** TODO.ai-platform/03 — the "my account" live-data delegation: the
|
|
43
47
|
* platform instance's API base + its client id at the OP (the
|
|
@@ -431,15 +431,35 @@ export function opMemberCanWrite(member: { via?: string; scope?: string } | null
|
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
/** The introspection answer → the member credential, judged against the
|
|
434
|
-
* deployment's
|
|
435
|
-
*
|
|
436
|
-
|
|
434
|
+
* deployment's two allowlists. The OP answers client-bearing tokens
|
|
435
|
+
* WITH client_id (the access-token table, the machine JWTs) and its
|
|
436
|
+
* PAT form WITHOUT one — a PAT's identity on the wire is its granted
|
|
437
|
+
* SERVICES (the PAT grammar's whole vocabulary). So the deployment
|
|
438
|
+
* names device-grant clients (OIDC_DEVICE_CLIENT_IDS) and, separately,
|
|
439
|
+
* the services whose personal access tokens it admits
|
|
440
|
+
* (OIDC_PAT_SERVICES). Both lists empty = the tier stays off; a token
|
|
441
|
+
* matching neither list resolves to null — never a widened guess. */
|
|
442
|
+
export function opMemberFromIntrospection(answer: any, ids: string[], patServices: string[] = []): OpMember | null {
|
|
437
443
|
if (!answer?.active) return null;
|
|
438
|
-
const clientId = String(answer.client_id ?? "");
|
|
439
|
-
if (!ids.includes(clientId)) return null;
|
|
440
444
|
const sub = String(answer.sub ?? "");
|
|
441
445
|
if (!sub) return null;
|
|
442
|
-
|
|
446
|
+
const clientId = String(answer.client_id ?? "");
|
|
447
|
+
if (clientId) {
|
|
448
|
+
if (!ids.includes(clientId)) return null;
|
|
449
|
+
return { sub, scope: String(answer.scope ?? ""), via: "op-token" };
|
|
450
|
+
}
|
|
451
|
+
if (!patServices.length) return null;
|
|
452
|
+
const scope = String(answer.scope ?? "");
|
|
453
|
+
const services = scope.split(/\s+/).filter(Boolean).map((s) => s.split(":")[0]);
|
|
454
|
+
if (!services.some((s) => patServices.includes(s))) return null;
|
|
455
|
+
return { sub, scope, via: "op-token" };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** The PAT-services allowlist (see opMemberFromIntrospection). */
|
|
459
|
+
export function patServiceIds(env: any): string[] {
|
|
460
|
+
return String(env.OIDC_PAT_SERVICES ?? "")
|
|
461
|
+
.split(/[\s,]+/)
|
|
462
|
+
.filter(Boolean);
|
|
443
463
|
}
|
|
444
464
|
|
|
445
465
|
/** The Bearer read + introspection, KV-cached 45s keyed by the token's
|
|
@@ -482,5 +502,5 @@ export async function opTokenMember(
|
|
|
482
502
|
/* cacheless = introspect per ask */
|
|
483
503
|
}
|
|
484
504
|
}
|
|
485
|
-
return opMemberFromIntrospection(answer, ids);
|
|
505
|
+
return opMemberFromIntrospection(answer, ids, patServiceIds(env));
|
|
486
506
|
}
|
|
@@ -40,9 +40,30 @@ export function answerQuality(qualities: (SourceQuality | undefined)[]): SourceQ
|
|
|
40
40
|
return worst;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** The one-line posture the interface renders under the answer.
|
|
43
|
+
/** The one-line posture the interface renders under the answer. The
|
|
44
|
+
* OCR rung is the owner's 2026-09-26 wording, verbatim. */
|
|
44
45
|
export function qualityNote(q: SourceQuality): string {
|
|
45
|
-
if (q === "verified") return "
|
|
46
|
-
if (q === "curated") return "
|
|
47
|
-
return "Partly grounded in experimental
|
|
46
|
+
if (q === "verified") return "High confidence: this publication's requirements ride a machine-checkable data model.";
|
|
47
|
+
if (q === "curated") return "Established sources: Metanorma-edited documents, chunked at clause boundaries.";
|
|
48
|
+
return "WARNING: Partly grounded in experimental data source that was derived from OCR content. Please verify content against official publications.";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The citations standing on the OCR rung, named for the reader — the
|
|
52
|
+
* confidence line says WHICH sources are experimental, not just that
|
|
53
|
+
* some are. Producer-UUID anchors say nothing and are dropped; a cap
|
|
54
|
+
* keeps the line readable on citation-heavy answers. */
|
|
55
|
+
export function experimentalSourceLabels(
|
|
56
|
+
cites: { quality?: string; docidentifier?: string; doc_id?: string; clause_anchor?: string }[],
|
|
57
|
+
cap = 6,
|
|
58
|
+
): string[] {
|
|
59
|
+
const labels: string[] = [];
|
|
60
|
+
for (const c of cites) {
|
|
61
|
+
if (c.quality !== "ocr") continue;
|
|
62
|
+
const id = String(c.docidentifier || c.doc_id || "source");
|
|
63
|
+
const anchor = String(c.clause_anchor ?? "");
|
|
64
|
+
const garbage = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(anchor) || (anchor.startsWith("_") && anchor.length > 12);
|
|
65
|
+
labels.push(garbage || !anchor || anchor === "overview" ? id : `${id} §${anchor}`);
|
|
66
|
+
if (labels.length >= cap) break;
|
|
67
|
+
}
|
|
68
|
+
return labels;
|
|
48
69
|
}
|