@konneal/engine 0.2.6 → 0.2.10
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-RX3W5JJW.js → ask-PKHGS2DZ.js} +2 -2
- package/dist/{chunk-GCL73ZLT.js → chunk-7UT76RQO.js} +11 -1
- package/dist/{chunk-NRC4FBDQ.js → chunk-YFDWKXEB.js} +1 -1
- package/dist/{chunk-7K5XP4PB.js → chunk-ZAEUQFC2.js} +123 -4
- package/dist/conditions.d.ts +30 -0
- package/dist/oidc.d.ts +3 -0
- package/dist/{search-OXHXXT5S.js → search-RFKT7ZZY.js} +2 -2
- package/dist/worker_public/src/index.js +5 -5
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +61 -4
- package/workers/worker_public/src/auth.ts +11 -0
- package/workers/worker_public/src/conditions.ts +123 -0
- package/workers/worker_public/src/index.ts +1 -1
- package/workers/worker_public/src/oidc.ts +4 -0
|
@@ -2294,6 +2294,7 @@ function buildAuthorizationUrl(metadata, params) {
|
|
|
2294
2294
|
url.searchParams.set("nonce", params.nonce);
|
|
2295
2295
|
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
2296
2296
|
url.searchParams.set("code_challenge_method", "S256");
|
|
2297
|
+
if (params.prompt) url.searchParams.set("prompt", params.prompt);
|
|
2297
2298
|
return url.toString();
|
|
2298
2299
|
}
|
|
2299
2300
|
async function exchangeCode(metadata, params) {
|
|
@@ -2473,13 +2474,19 @@ async function handleLogin(env, req) {
|
|
|
2473
2474
|
expirationTtl: 600
|
|
2474
2475
|
}
|
|
2475
2476
|
);
|
|
2477
|
+
const silent = url0.searchParams.get("prompt") === "none";
|
|
2476
2478
|
const url = buildAuthorizationUrl(meta, {
|
|
2477
2479
|
clientId: cfg.clientId,
|
|
2478
2480
|
redirectUri: cfg.redirectUri,
|
|
2479
2481
|
scopes: "openid profile email roles",
|
|
2480
2482
|
state,
|
|
2481
2483
|
nonce,
|
|
2482
|
-
codeChallenge: pkce.challenge
|
|
2484
|
+
codeChallenge: pkce.challenge,
|
|
2485
|
+
// silent SSO: the OP answers from its existing session or errors
|
|
2486
|
+
// login_required — the callback then lands quietly, signed in or
|
|
2487
|
+
// still anonymous, and the estate session carries to this site
|
|
2488
|
+
// without a click
|
|
2489
|
+
...silent ? { prompt: "none" } : {}
|
|
2483
2490
|
});
|
|
2484
2491
|
return new Response(null, { status: 302, headers: { location: url } });
|
|
2485
2492
|
} catch (e) {
|
|
@@ -2491,6 +2498,9 @@ async function handleCallback(env, req) {
|
|
|
2491
2498
|
if (!cfg) return redirectWithError("not_configured");
|
|
2492
2499
|
const url = new URL(req.url);
|
|
2493
2500
|
const opError = url.searchParams.get("error");
|
|
2501
|
+
if (opError === "login_required") {
|
|
2502
|
+
return new Response(null, { status: 302, headers: { location: "/?auth_silent=none" } });
|
|
2503
|
+
}
|
|
2494
2504
|
if (opError) {
|
|
2495
2505
|
const msg = opError === "access_denied" ? "Sign-in was cancelled." : opError === "temporarily_unavailable" ? "The sign-in service is busy. Please try again in a moment." : "The sign-in service reported a problem. Please try again.";
|
|
2496
2506
|
return new Response(null, {
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
syntheticUnderstanding,
|
|
28
28
|
telemetry,
|
|
29
29
|
understandQuery
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-7UT76RQO.js";
|
|
31
31
|
import {
|
|
32
32
|
corsHeaders,
|
|
33
33
|
err,
|
|
@@ -759,6 +759,83 @@ function verdictNote(v, node) {
|
|
|
759
759
|
return lines.join("\n");
|
|
760
760
|
}
|
|
761
761
|
|
|
762
|
+
// workers/worker_public/src/conditions.ts
|
|
763
|
+
var NUM = String.raw`-?\d+(?:[.,]\d+)?`;
|
|
764
|
+
function quantitiesIn(query) {
|
|
765
|
+
const out = {};
|
|
766
|
+
const num2 = (s) => Number(s.replace(",", "."));
|
|
767
|
+
const temp = query.match(new RegExp(`(${NUM})\\s*(?:\xB0\\s*)?(?:C\\b|degC\\b|celsius)`, "i"));
|
|
768
|
+
if (temp) out.temperature = num2(temp[1]);
|
|
769
|
+
const rh = query.match(new RegExp(`(${NUM})\\s*%\\s*(?:RH\\b|relative\\s+humidity)?`, "i"));
|
|
770
|
+
if (rh) out.relative_humidity = num2(rh[1]);
|
|
771
|
+
const hours = query.match(new RegExp(`(${NUM})\\s*h\\b`, "i"));
|
|
772
|
+
if (hours) out.duration = num2(hours[1]);
|
|
773
|
+
const days = query.match(new RegExp(`(${NUM})\\s*days?\\b`, "i"));
|
|
774
|
+
if (days && out.duration === void 0) out.duration = num2(days[1]) * 24;
|
|
775
|
+
return out;
|
|
776
|
+
}
|
|
777
|
+
function entryBand(e) {
|
|
778
|
+
const value = Number(String(e.value).replace(",", "."));
|
|
779
|
+
const tolerance = Number(String(e.tolerance ?? "0").replace(",", "."));
|
|
780
|
+
if (!Number.isFinite(value)) return null;
|
|
781
|
+
return { value, tolerance: Number.isFinite(tolerance) ? tolerance : 0, lo: value - tolerance, hi: value + tolerance };
|
|
782
|
+
}
|
|
783
|
+
function scoreSet(entries, q) {
|
|
784
|
+
const checks = [];
|
|
785
|
+
let distance = 0;
|
|
786
|
+
let stated = 0;
|
|
787
|
+
for (const e of entries) {
|
|
788
|
+
const statedValue = q[e.quantity_kind];
|
|
789
|
+
if (statedValue === void 0) continue;
|
|
790
|
+
const band = entryBand(e);
|
|
791
|
+
if (!band) continue;
|
|
792
|
+
const in_band = statedValue >= band.lo && statedValue <= band.hi;
|
|
793
|
+
const gap = Math.max(0, statedValue - band.hi, band.lo - statedValue);
|
|
794
|
+
distance += gap / Math.max(band.tolerance, 1);
|
|
795
|
+
stated += 1;
|
|
796
|
+
checks.push({
|
|
797
|
+
quantity_kind: e.quantity_kind,
|
|
798
|
+
band: `${band.value} ${e.unit} \xB1${band.tolerance}`,
|
|
799
|
+
stated: statedValue,
|
|
800
|
+
in_band
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
return stated ? { checks, distance, stated } : null;
|
|
804
|
+
}
|
|
805
|
+
function evaluateConditionSets(nodes, query) {
|
|
806
|
+
const q = quantitiesIn(query);
|
|
807
|
+
const kinds = Object.keys(q);
|
|
808
|
+
if (!kinds.length) return null;
|
|
809
|
+
const scored = [];
|
|
810
|
+
for (const n of nodes) {
|
|
811
|
+
const c = n.content && typeof n.content === "object" ? n.content : {};
|
|
812
|
+
const payload = c.payload ?? {};
|
|
813
|
+
const entries = Array.isArray(payload.entries) ? payload.entries : [];
|
|
814
|
+
if (!entries.length) continue;
|
|
815
|
+
const s = scoreSet(entries, q);
|
|
816
|
+
if (s) scored.push({ node_id: n.node_id, checks: s.checks, distance: s.distance });
|
|
817
|
+
}
|
|
818
|
+
if (!scored.length) return null;
|
|
819
|
+
const matched = scored.filter((s) => s.checks.every((c) => c.in_band));
|
|
820
|
+
scored.sort((a, b) => a.distance - b.distance);
|
|
821
|
+
if (matched.length) {
|
|
822
|
+
return {
|
|
823
|
+
verdict: "pass",
|
|
824
|
+
matched: matched.map((m) => m.node_id),
|
|
825
|
+
checks: matched[0].checks,
|
|
826
|
+
note: `VERDICT: PASS \u2014 the stated combination (${kinds.join(", ")}) matches severity set(s) ${matched.map((m) => m.node_id).join(", ")}. Present this verdict and cite the set's clause.`
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
const nearest = scored[0];
|
|
830
|
+
return {
|
|
831
|
+
verdict: "fail",
|
|
832
|
+
matched: [],
|
|
833
|
+
nearest: { node_id: nearest.node_id, distance: Number(nearest.distance.toFixed(2)), bands: nearest.checks.map((c) => c.band) },
|
|
834
|
+
checks: scored[0].checks,
|
|
835
|
+
note: `VERDICT: FAIL \u2014 no severity set admits the stated combination. The nearest set is ${nearest.node_id} (bands: ${nearest.checks.map((c) => c.band).join("; ")}). Say the combination is outside the menu and name the nearest set; never soften it.`
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
762
839
|
// workers/worker_public/src/drafts.ts
|
|
763
840
|
var ACT_VERB = "(?:draft|prepare|pre-?fill|fill\\s+(?:in|out)|start|submit|file|lodge)";
|
|
764
841
|
var ACT_TARGET = "(?:new\\s+)?(?:certification\\s+|type[ -]evaluation\\s+|OIML[- ]CS\\s+)?application";
|
|
@@ -1551,6 +1628,32 @@ ${summary}` }] : [],
|
|
|
1551
1628
|
const modelNote = boundModel && !boundModel.gated ? modelGroundingBlock(boundModel) : void 0;
|
|
1552
1629
|
const machineVerdict = boundModel && !boundModel.gated ? evaluate(boundModel.content, q.query) : null;
|
|
1553
1630
|
const machineNote = machineVerdict && boundModel ? verdictNote(machineVerdict, boundModel) : void 0;
|
|
1631
|
+
let conditionVerdict = null;
|
|
1632
|
+
let conditionStandard = null;
|
|
1633
|
+
if (!machineVerdict && !boundModel && P().publisher.features?.model_plane) {
|
|
1634
|
+
const ql = q.query.toLowerCase();
|
|
1635
|
+
const severityWord = /\b(severity|test|valid|tolerance|condition|within)\b/.test(ql);
|
|
1636
|
+
const stated = quantitiesIn(q.query);
|
|
1637
|
+
if (severityWord && Object.keys(stated).length >= 1) {
|
|
1638
|
+
const docNum = modelDocHint?.doc_number;
|
|
1639
|
+
const sql = docNum ? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' AND standard = ?1" : "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' LIMIT 40";
|
|
1640
|
+
const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
|
|
1641
|
+
const rows = await stmt.all().catch(() => ({ results: [] }));
|
|
1642
|
+
const candidates = (rows.results ?? []).filter((r) => {
|
|
1643
|
+
const entry = licensedEntryForPackage(String(r.standard));
|
|
1644
|
+
return !entry || (standardKeys?.has(entry.key) ?? false);
|
|
1645
|
+
});
|
|
1646
|
+
const v = evaluateConditionSets(
|
|
1647
|
+
candidates.map((r) => ({ node_id: String(r.node_id), content: JSON.parse(String(r.content ?? "{}")) })),
|
|
1648
|
+
q.query
|
|
1649
|
+
);
|
|
1650
|
+
if (v) {
|
|
1651
|
+
conditionVerdict = v;
|
|
1652
|
+
conditionStandard = String(rows.results?.[0]?.standard ?? "");
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
const conditionNote = conditionVerdict && conditionStandard ? `${conditionVerdict.note} (computed from the ${conditionStandard} condition sets \u2014 machine evaluation, cite the package's clause.)` : void 0;
|
|
1554
1657
|
const verdictBlock = machineVerdict ? {
|
|
1555
1658
|
unit_id: boundModel.node_id,
|
|
1556
1659
|
type: "verdict",
|
|
@@ -1564,6 +1667,22 @@ ${summary}` }] : [],
|
|
|
1564
1667
|
}
|
|
1565
1668
|
} : null;
|
|
1566
1669
|
if (machineVerdict) console.log("verdict engine:", boundModel.node_id, "\u2192", machineVerdict.verdict.toUpperCase(), machineVerdict.missing.length ? `(missing ${machineVerdict.missing.join(",")})` : "");
|
|
1670
|
+
const conditionBlock = conditionVerdict ? {
|
|
1671
|
+
unit_id: conditionVerdict.matched[0] ?? conditionVerdict.nearest.node_id,
|
|
1672
|
+
type: "verdict",
|
|
1673
|
+
docidentifier: `IEC SMART model (${conditionStandard})`,
|
|
1674
|
+
payload: {
|
|
1675
|
+
verdict: conditionVerdict.verdict,
|
|
1676
|
+
missing: [],
|
|
1677
|
+
checks: conditionVerdict.checks.map((c) => ({
|
|
1678
|
+
expression: `${c.quantity_kind} within ${c.band}`,
|
|
1679
|
+
values: { stated: c.stated },
|
|
1680
|
+
result: c.in_band
|
|
1681
|
+
})),
|
|
1682
|
+
...conditionVerdict.nearest ? { nearest: conditionVerdict.nearest } : { matched: conditionVerdict.matched }
|
|
1683
|
+
}
|
|
1684
|
+
} : null;
|
|
1685
|
+
if (conditionVerdict) console.log("condition engine:", conditionVerdict.matched.join("|") || conditionVerdict.nearest.node_id, "\u2192", conditionVerdict.verdict.toUpperCase());
|
|
1567
1686
|
try {
|
|
1568
1687
|
const tR = Date.now();
|
|
1569
1688
|
if (declaredCtx?.kind === "account") {
|
|
@@ -1657,7 +1776,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
1657
1776
|
q.lang,
|
|
1658
1777
|
keptHistory,
|
|
1659
1778
|
// stage-extracted graph facts (GraphRAG) ride the same note channel
|
|
1660
|
-
[processNote, eNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, licenseNote, ...retrieved.notes ?? []].filter(Boolean).join("\n") || void 0,
|
|
1779
|
+
[processNote, eNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, licenseNote, ...retrieved.notes ?? []].filter(Boolean).join("\n") || void 0,
|
|
1661
1780
|
summary,
|
|
1662
1781
|
budget
|
|
1663
1782
|
);
|
|
@@ -1705,7 +1824,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
1705
1824
|
model,
|
|
1706
1825
|
query_hash: queryHash,
|
|
1707
1826
|
follow_ups: understanding?.follow_ups ?? [],
|
|
1708
|
-
blocks:
|
|
1827
|
+
blocks: [...c2.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : []],
|
|
1709
1828
|
context_applied: ctxApplied,
|
|
1710
1829
|
read: readAs(),
|
|
1711
1830
|
// the evidence view's ground truth: the exact passages this
|
|
@@ -1822,7 +1941,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
1822
1941
|
if (completionBlocks.length) console.log("contract completion:", completionBlocks.length, "table block(s) attached server-side");
|
|
1823
1942
|
}
|
|
1824
1943
|
completionBlocks.push(...await completeFigures(env.DB, answer, [...c2ns.blocks, ...completionBlocks], used));
|
|
1825
|
-
const out = { answer, citations: finalCites, model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...verdictBlock ? [verdictBlock] : [], ...completionBlocks], context_applied: ctxApplied, ...liveRecords ? { records: liveRecords } : {} };
|
|
1944
|
+
const out = { answer, citations: finalCites, model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...verdictBlock ? [verdictBlock] : [], ...conditionBlock ? [conditionBlock] : [], ...completionBlocks], context_applied: ctxApplied, ...liveRecords ? { records: liveRecords } : {} };
|
|
1826
1945
|
const cacheable = !contextual && !declaredCtx && !answer.includes(refusalAnswer()) && finalAnchors.violations.length === 0;
|
|
1827
1946
|
if (cacheable) {
|
|
1828
1947
|
const warmVec = await warmEmbed ?? null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ConditionCheck {
|
|
2
|
+
quantity_kind: string;
|
|
3
|
+
band: string;
|
|
4
|
+
stated: number;
|
|
5
|
+
in_band: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ConditionVerdict {
|
|
8
|
+
verdict: "pass" | "fail";
|
|
9
|
+
matched: string[];
|
|
10
|
+
nearest?: {
|
|
11
|
+
node_id: string;
|
|
12
|
+
distance: number;
|
|
13
|
+
bands: string[];
|
|
14
|
+
};
|
|
15
|
+
checks: ConditionCheck[];
|
|
16
|
+
note: string;
|
|
17
|
+
}
|
|
18
|
+
/** The question's stated quantities, normalized onto the entries'
|
|
19
|
+
* quantity kinds by UNIT (degC/K → temperature; % / RH →
|
|
20
|
+
* relative_humidity; h/days → duration). Units, never words: "12
|
|
21
|
+
* months" is not a 12 h duration. */
|
|
22
|
+
export declare function quantitiesIn(query: string): Record<string, number>;
|
|
23
|
+
/** Evaluate the candidate condition_set nodes against the question's
|
|
24
|
+
* stated quantities. PASS when at least one set admits every stated
|
|
25
|
+
* quantity within its band (the set is named); FAIL names the nearest
|
|
26
|
+
* set and the violated bands. */
|
|
27
|
+
export declare function evaluateConditionSets(nodes: {
|
|
28
|
+
node_id: string;
|
|
29
|
+
content: unknown;
|
|
30
|
+
}[], query: string): ConditionVerdict | null;
|
package/dist/oidc.d.ts
CHANGED
|
@@ -30,6 +30,9 @@ export declare function buildAuthorizationUrl(metadata: OidcMetadata, params: {
|
|
|
30
30
|
state: string;
|
|
31
31
|
nonce: string;
|
|
32
32
|
codeChallenge: string;
|
|
33
|
+
/** OIDC prompt ('none' = silent: the OP answers from its session or
|
|
34
|
+
* errors login_required — never an interaction). */
|
|
35
|
+
prompt?: string;
|
|
33
36
|
}): string;
|
|
34
37
|
export interface OidcTokenResponse {
|
|
35
38
|
id_token: string;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleSearch
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-YFDWKXEB.js";
|
|
4
4
|
import {
|
|
5
5
|
checkQuoteAnchors,
|
|
6
6
|
handleAsk,
|
|
7
7
|
handleMemories,
|
|
8
8
|
scoreJudge,
|
|
9
9
|
standardForDocNumber
|
|
10
|
-
} from "../../chunk-
|
|
10
|
+
} from "../../chunk-ZAEUQFC2.js";
|
|
11
11
|
import {
|
|
12
12
|
buildMessages,
|
|
13
13
|
citations,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
sessionFrom,
|
|
30
30
|
telemetry,
|
|
31
31
|
understandQuery
|
|
32
|
-
} from "../../chunk-
|
|
32
|
+
} from "../../chunk-7UT76RQO.js";
|
|
33
33
|
import {
|
|
34
34
|
authenticate,
|
|
35
35
|
corsHeaders,
|
|
@@ -979,7 +979,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
979
979
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
980
980
|
body: JSON.stringify({ ...args, stream: false })
|
|
981
981
|
});
|
|
982
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
982
|
+
const res = name === "ask" ? await (await import("../../ask-PKHGS2DZ.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-RFKT7ZZY.js")).handleSearch(env, ctx, inner, tier, key);
|
|
983
983
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
984
984
|
});
|
|
985
985
|
if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
|
|
@@ -1602,7 +1602,7 @@ function permissionsCatalog() {
|
|
|
1602
1602
|
perms.push({ id: "ai.memories", description: "Personalized memory files on the assistant" });
|
|
1603
1603
|
perms.push({ id: "ai.research", description: "Deep-research multi-pass questions" });
|
|
1604
1604
|
perms.push({ id: "ai.keys.admin", description: "Create and revoke API keys" });
|
|
1605
|
-
groups.push({ id: "ai", description:
|
|
1605
|
+
groups.push({ id: "ai", description: `${P().publisher.product_name} gated capabilities`, permissions: perms });
|
|
1606
1606
|
return {
|
|
1607
1607
|
version: 1,
|
|
1608
1608
|
verbs: ["read", "write", "admin"],
|
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@oimlsmart/oiml-pubid": "^1.2.1"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {},
|
|
37
|
-
"version": "0.2.
|
|
37
|
+
"version": "0.2.10",
|
|
38
38
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
39
39
|
"license": "BSD-3-Clause",
|
|
40
40
|
"type": "module",
|
|
@@ -19,8 +19,9 @@ import { contractV2, tableRetyped } from "./refs";
|
|
|
19
19
|
import { completeTables, completeFigures } from "./completion";
|
|
20
20
|
import { NO_CONTEXT, appliedContext, contextNote, namedDocumentIn, parseContext, resolveDocScope, syntheticUnderstanding } from "./context";
|
|
21
21
|
import { exchangeForLiveToken, liveDataConfig, resolveLiveAccount, type LiveRecord } from "./livedata";
|
|
22
|
-
import { bindModelNode, licenseBoundaryNote, licenseBoundaryRefusal, modelCitation, modelEcho, modelGroundingBlock, modelNodeRefIn, standardForDocNumber } from "./modelplane";
|
|
22
|
+
import { bindModelNode, licenseBoundaryNote, licenseBoundaryRefusal, licensedEntryForPackage, modelCitation, modelEcho, modelGroundingBlock, modelNodeRefIn, standardForDocNumber } from "./modelplane";
|
|
23
23
|
import { evaluate as machineEvaluate, verdictNote } from "./verdict";
|
|
24
|
+
import { evaluateConditionSets, quantitiesIn, type ConditionVerdict } from "./conditions";
|
|
24
25
|
import { detectDraftIntent, prepareDraft } from "./drafts";
|
|
25
26
|
import { memoryNote } from "./memories";
|
|
26
27
|
import { entitlementScope, resolveRequestScope, requestSalt } from "./requestScope";
|
|
@@ -717,6 +718,42 @@ async function handleAsk(
|
|
|
717
718
|
// and the verdict BLOCK is server-built — data, never generated prose
|
|
718
719
|
const machineVerdict = boundModel && !boundModel.gated ? machineEvaluate(boundModel.content, q.query) : null;
|
|
719
720
|
const machineNote = machineVerdict && boundModel ? verdictNote(machineVerdict, boundModel) : undefined;
|
|
721
|
+
// ── condition-set membership (konneal/engine#90): the test-method
|
|
722
|
+
// packages' severity menus as machine-verifiable membership. Fires
|
|
723
|
+
// when no explicit node binding ran and the question states
|
|
724
|
+
// quantities alongside severity vocabulary; the candidate sets come
|
|
725
|
+
// from the model node store (kind condition_set), license-gated like
|
|
726
|
+
// every model lane.
|
|
727
|
+
let conditionVerdict: ConditionVerdict | null = null;
|
|
728
|
+
let conditionStandard: string | null = null;
|
|
729
|
+
if (!machineVerdict && !boundModel && P().publisher.features?.model_plane) {
|
|
730
|
+
const ql = q.query.toLowerCase();
|
|
731
|
+
const severityWord = /\b(severity|test|valid|tolerance|condition|within)\b/.test(ql);
|
|
732
|
+
const stated = quantitiesIn(q.query);
|
|
733
|
+
if (severityWord && Object.keys(stated).length >= 1) {
|
|
734
|
+
const docNum = modelDocHint?.doc_number;
|
|
735
|
+
const sql = docNum
|
|
736
|
+
? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' AND standard = ?1"
|
|
737
|
+
: "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' LIMIT 40";
|
|
738
|
+
const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
|
|
739
|
+
const rows = await stmt.all().catch(() => ({ results: [] }));
|
|
740
|
+
const candidates = (rows.results ?? []).filter((r: any) => {
|
|
741
|
+
const entry = licensedEntryForPackage(String(r.standard));
|
|
742
|
+
return !entry || (standardKeys?.has(entry.key) ?? false);
|
|
743
|
+
});
|
|
744
|
+
const v = evaluateConditionSets(
|
|
745
|
+
candidates.map((r: any) => ({ node_id: String(r.node_id), content: JSON.parse(String(r.content ?? "{}")) })),
|
|
746
|
+
q.query,
|
|
747
|
+
);
|
|
748
|
+
if (v) {
|
|
749
|
+
conditionVerdict = v;
|
|
750
|
+
conditionStandard = String((rows.results?.[0] as any)?.standard ?? "");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
const conditionNote = conditionVerdict && conditionStandard
|
|
755
|
+
? `${conditionVerdict.note} (computed from the ${conditionStandard} condition sets — machine evaluation, cite the package's clause.)`
|
|
756
|
+
: undefined;
|
|
720
757
|
const verdictBlock = machineVerdict
|
|
721
758
|
? {
|
|
722
759
|
unit_id: boundModel!.node_id,
|
|
@@ -732,6 +769,26 @@ async function handleAsk(
|
|
|
732
769
|
}
|
|
733
770
|
: null;
|
|
734
771
|
if (machineVerdict) console.log("verdict engine:", boundModel!.node_id, "→", machineVerdict.verdict.toUpperCase(), machineVerdict.missing.length ? `(missing ${machineVerdict.missing.join(",")})` : "");
|
|
772
|
+
const conditionBlock = conditionVerdict
|
|
773
|
+
? {
|
|
774
|
+
unit_id: conditionVerdict.matched[0] ?? conditionVerdict.nearest!.node_id,
|
|
775
|
+
type: "verdict",
|
|
776
|
+
docidentifier: `IEC SMART model (${conditionStandard})`,
|
|
777
|
+
payload: {
|
|
778
|
+
verdict: conditionVerdict.verdict,
|
|
779
|
+
missing: [],
|
|
780
|
+
checks: conditionVerdict.checks.map((c) => ({
|
|
781
|
+
expression: `${c.quantity_kind} within ${c.band}`,
|
|
782
|
+
values: { stated: c.stated },
|
|
783
|
+
result: c.in_band,
|
|
784
|
+
})),
|
|
785
|
+
...(conditionVerdict.nearest
|
|
786
|
+
? { nearest: conditionVerdict.nearest }
|
|
787
|
+
: { matched: conditionVerdict.matched }),
|
|
788
|
+
},
|
|
789
|
+
}
|
|
790
|
+
: null;
|
|
791
|
+
if (conditionVerdict) console.log("condition engine:", conditionVerdict.matched.join("|") || conditionVerdict.nearest!.node_id, "→", conditionVerdict.verdict.toUpperCase());
|
|
735
792
|
try {
|
|
736
793
|
const tR = Date.now();
|
|
737
794
|
// ── The "my account" live read (TODO.ai-platform/03) — resolved
|
|
@@ -881,7 +938,7 @@ async function handleAsk(
|
|
|
881
938
|
q.lang,
|
|
882
939
|
keptHistory,
|
|
883
940
|
// stage-extracted graph facts (GraphRAG) ride the same note channel
|
|
884
|
-
[processNote, eNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, licenseNote, ...(retrieved.notes ?? [])].filter(Boolean).join("\n") || undefined,
|
|
941
|
+
[processNote, eNote, contextNote(declaredCtx, docScope), accountNote, modelNote, vocabNote, memNote, machineNote, conditionNote, licenseNote, ...(retrieved.notes ?? [])].filter(Boolean).join("\n") || undefined,
|
|
885
942
|
summary,
|
|
886
943
|
budget,
|
|
887
944
|
);
|
|
@@ -934,7 +991,7 @@ async function handleAsk(
|
|
|
934
991
|
const c2 = canonical0.includes(refusalAnswer())
|
|
935
992
|
? { text: canonical0, blocks: [], dropped: [] as string[] }
|
|
936
993
|
: await contractV2(env.DB, canonical0, usedHits);
|
|
937
|
-
send({ type: "done", model, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks:
|
|
994
|
+
send({ type: "done", model, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : [])], context_applied: ctxApplied, read: readAs(),
|
|
938
995
|
// the evidence view's ground truth: the exact passages this
|
|
939
996
|
// answer was built from, compact — cache hits carry none,
|
|
940
997
|
// because the cache stores the answer and never the passages
|
|
@@ -1113,7 +1170,7 @@ async function handleAsk(
|
|
|
1113
1170
|
// figure completion (#172) — see ./completion for the rationale
|
|
1114
1171
|
completionBlocks.push(...(await completeFigures(env.DB, answer, [...c2ns.blocks, ...completionBlocks], used)));
|
|
1115
1172
|
|
|
1116
|
-
const out = { answer, citations: finalCites, model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...(verdictBlock ? [verdictBlock] : []), ...completionBlocks], context_applied: ctxApplied, ...(liveRecords ? { records: liveRecords } : {}) };
|
|
1173
|
+
const out = { answer, citations: finalCites, model: MODELS.member, query_hash: queryHash, follow_ups: understanding?.follow_ups ?? [], blocks: [...c2ns.blocks, ...(verdictBlock ? [verdictBlock] : []), ...(conditionBlock ? [conditionBlock] : []), ...completionBlocks], context_applied: ctxApplied, ...(liveRecords ? { records: liveRecords } : {}) };
|
|
1117
1174
|
const cacheable = !contextual && !declaredCtx && !answer.includes(refusalAnswer()) && finalAnchors.violations.length === 0;
|
|
1118
1175
|
if (cacheable) {
|
|
1119
1176
|
const warmVec = (await warmEmbed) ?? null;
|
|
@@ -85,6 +85,7 @@ export async function handleLogin(env: any, req: Request): Promise<Response> {
|
|
|
85
85
|
expirationTtl: 600,
|
|
86
86
|
},
|
|
87
87
|
);
|
|
88
|
+
const silent = url0.searchParams.get("prompt") === "none";
|
|
88
89
|
const url = buildAuthorizationUrl(meta, {
|
|
89
90
|
clientId: cfg.clientId,
|
|
90
91
|
redirectUri: cfg.redirectUri,
|
|
@@ -92,6 +93,11 @@ export async function handleLogin(env: any, req: Request): Promise<Response> {
|
|
|
92
93
|
state,
|
|
93
94
|
nonce,
|
|
94
95
|
codeChallenge: pkce.challenge,
|
|
96
|
+
// silent SSO: the OP answers from its existing session or errors
|
|
97
|
+
// login_required — the callback then lands quietly, signed in or
|
|
98
|
+
// still anonymous, and the estate session carries to this site
|
|
99
|
+
// without a click
|
|
100
|
+
...(silent ? { prompt: "none" } : {}),
|
|
95
101
|
});
|
|
96
102
|
return new Response(null, { status: 302, headers: { location: url } });
|
|
97
103
|
} catch (e) {
|
|
@@ -104,6 +110,11 @@ export async function handleCallback(env: any, req: Request): Promise<Response>
|
|
|
104
110
|
if (!cfg) return redirectWithError("not_configured");
|
|
105
111
|
const url = new URL(req.url);
|
|
106
112
|
const opError = url.searchParams.get("error");
|
|
113
|
+
if (opError === "login_required") {
|
|
114
|
+
// the silent probe found no OP session — anonymous is the honest
|
|
115
|
+
// state, never an error
|
|
116
|
+
return new Response(null, { status: 302, headers: { location: "/?auth_silent=none" } });
|
|
117
|
+
}
|
|
107
118
|
if (opError) {
|
|
108
119
|
// the OP redirected back with its own failure (user denied consent,
|
|
109
120
|
// session expired at the OP, …) — fail closed, in plain language
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Condition-set evaluation (konneal/engine#90): the severity menus of
|
|
2
|
+
// the test-method packages as machine-verifiable membership. A
|
|
3
|
+
// condition_set node's payload.entries carry the kernel's quantity
|
|
4
|
+
// doctrine — value + unit inseparable, tolerance the SPECIFIED band —
|
|
5
|
+
// so "is 85 % RH at 40 °C a valid Test Cab severity?" computes: find
|
|
6
|
+
// the sets whose temperature AND relative_humidity bands admit the
|
|
7
|
+
// stated pair. The verdict is executed data; the model narrates it.
|
|
8
|
+
|
|
9
|
+
export interface ConditionCheck {
|
|
10
|
+
quantity_kind: string;
|
|
11
|
+
band: string;
|
|
12
|
+
stated: number;
|
|
13
|
+
in_band: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ConditionVerdict {
|
|
17
|
+
verdict: "pass" | "fail";
|
|
18
|
+
matched: string[];
|
|
19
|
+
nearest?: { node_id: string; distance: number; bands: string[] };
|
|
20
|
+
checks: ConditionCheck[];
|
|
21
|
+
note: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Entry {
|
|
25
|
+
quantity_kind: string;
|
|
26
|
+
value: string | number;
|
|
27
|
+
unit: string;
|
|
28
|
+
tolerance: string | number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const NUM = String.raw`-?\d+(?:[.,]\d+)?`;
|
|
32
|
+
|
|
33
|
+
/** The question's stated quantities, normalized onto the entries'
|
|
34
|
+
* quantity kinds by UNIT (degC/K → temperature; % / RH →
|
|
35
|
+
* relative_humidity; h/days → duration). Units, never words: "12
|
|
36
|
+
* months" is not a 12 h duration. */
|
|
37
|
+
export function quantitiesIn(query: string): Record<string, number> {
|
|
38
|
+
const out: Record<string, number> = {};
|
|
39
|
+
const num = (s: string) => Number(s.replace(",", "."));
|
|
40
|
+
const temp = query.match(new RegExp(`(${NUM})\\s*(?:°\\s*)?(?:C\\b|degC\\b|celsius)`, "i"));
|
|
41
|
+
if (temp) out.temperature = num(temp[1]!);
|
|
42
|
+
const rh = query.match(new RegExp(`(${NUM})\\s*%\\s*(?:RH\\b|relative\\s+humidity)?`, "i"));
|
|
43
|
+
if (rh) out.relative_humidity = num(rh[1]!);
|
|
44
|
+
const hours = query.match(new RegExp(`(${NUM})\\s*h\\b`, "i"));
|
|
45
|
+
if (hours) out.duration = num(hours[1]!);
|
|
46
|
+
const days = query.match(new RegExp(`(${NUM})\\s*days?\\b`, "i"));
|
|
47
|
+
if (days && out.duration === undefined) out.duration = num(days[1]!) * 24;
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function entryBand(e: Entry): { lo: number; hi: number; value: number; tolerance: number } | null {
|
|
52
|
+
const value = Number(String(e.value).replace(",", "."));
|
|
53
|
+
const tolerance = Number(String(e.tolerance ?? "0").replace(",", "."));
|
|
54
|
+
if (!Number.isFinite(value)) return null;
|
|
55
|
+
return { value, tolerance: Number.isFinite(tolerance) ? tolerance : 0, lo: value - tolerance, hi: value + tolerance };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Evaluate ONE set: every stated quantity in-band? Sets with entries
|
|
59
|
+
* whose kind the question never states are skipped for that kind (a
|
|
60
|
+
* question stating one quantity does not fail a two-entry set). */
|
|
61
|
+
function scoreSet(entries: Entry[], q: Record<string, number>): { checks: ConditionCheck[]; distance: number; stated: number } | null {
|
|
62
|
+
const checks: ConditionCheck[] = [];
|
|
63
|
+
let distance = 0;
|
|
64
|
+
let stated = 0;
|
|
65
|
+
for (const e of entries) {
|
|
66
|
+
const statedValue = q[e.quantity_kind];
|
|
67
|
+
if (statedValue === undefined) continue;
|
|
68
|
+
const band = entryBand(e);
|
|
69
|
+
if (!band) continue;
|
|
70
|
+
const in_band = statedValue >= band.lo && statedValue <= band.hi;
|
|
71
|
+
const gap = Math.max(0, statedValue - band.hi, band.lo - statedValue);
|
|
72
|
+
distance += gap / Math.max(band.tolerance, 1);
|
|
73
|
+
stated += 1;
|
|
74
|
+
checks.push({
|
|
75
|
+
quantity_kind: e.quantity_kind,
|
|
76
|
+
band: `${band.value} ${e.unit} ±${band.tolerance}`,
|
|
77
|
+
stated: statedValue,
|
|
78
|
+
in_band,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return stated ? { checks, distance, stated } : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Evaluate the candidate condition_set nodes against the question's
|
|
85
|
+
* stated quantities. PASS when at least one set admits every stated
|
|
86
|
+
* quantity within its band (the set is named); FAIL names the nearest
|
|
87
|
+
* set and the violated bands. */
|
|
88
|
+
export function evaluateConditionSets(
|
|
89
|
+
nodes: { node_id: string; content: unknown }[],
|
|
90
|
+
query: string,
|
|
91
|
+
): ConditionVerdict | null {
|
|
92
|
+
const q = quantitiesIn(query);
|
|
93
|
+
const kinds = Object.keys(q);
|
|
94
|
+
if (!kinds.length) return null;
|
|
95
|
+
const scored: { node_id: string; checks: ConditionCheck[]; distance: number }[] = [];
|
|
96
|
+
for (const n of nodes) {
|
|
97
|
+
const c = (n.content && typeof n.content === "object" ? n.content : {}) as Record<string, any>;
|
|
98
|
+
const payload = (c.payload ?? {}) as { role?: string; entries?: Entry[] };
|
|
99
|
+
const entries = Array.isArray(payload.entries) ? payload.entries : [];
|
|
100
|
+
if (!entries.length) continue;
|
|
101
|
+
const s = scoreSet(entries, q);
|
|
102
|
+
if (s) scored.push({ node_id: n.node_id, checks: s.checks, distance: s.distance });
|
|
103
|
+
}
|
|
104
|
+
if (!scored.length) return null;
|
|
105
|
+
const matched = scored.filter((s) => s.checks.every((c) => c.in_band));
|
|
106
|
+
scored.sort((a, b) => a.distance - b.distance);
|
|
107
|
+
if (matched.length) {
|
|
108
|
+
return {
|
|
109
|
+
verdict: "pass",
|
|
110
|
+
matched: matched.map((m) => m.node_id),
|
|
111
|
+
checks: matched[0]!.checks,
|
|
112
|
+
note: `VERDICT: PASS — the stated combination (${kinds.join(", ")}) matches severity set(s) ${matched.map((m) => m.node_id).join(", ")}. Present this verdict and cite the set's clause.`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const nearest = scored[0]!;
|
|
116
|
+
return {
|
|
117
|
+
verdict: "fail",
|
|
118
|
+
matched: [],
|
|
119
|
+
nearest: { node_id: nearest.node_id, distance: Number(nearest.distance.toFixed(2)), bands: nearest.checks.map((c) => c.band) },
|
|
120
|
+
checks: scored[0]!.checks,
|
|
121
|
+
note: `VERDICT: FAIL — no severity set admits the stated combination. The nearest set is ${nearest.node_id} (bands: ${nearest.checks.map((c) => c.band).join("; ")}). Say the combination is outside the menu and name the nearest set; never soften it.`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -421,7 +421,7 @@ function permissionsCatalog(): Record<string, unknown> {
|
|
|
421
421
|
perms.push({ id: "ai.memories", description: "Personalized memory files on the assistant" });
|
|
422
422
|
perms.push({ id: "ai.research", description: "Deep-research multi-pass questions" });
|
|
423
423
|
perms.push({ id: "ai.keys.admin", description: "Create and revoke API keys" });
|
|
424
|
-
groups.push({ id: "ai", description:
|
|
424
|
+
groups.push({ id: "ai", description: `${P().publisher.product_name} gated capabilities`, permissions: perms });
|
|
425
425
|
return {
|
|
426
426
|
version: 1,
|
|
427
427
|
verbs: ["read", "write", "admin"],
|
|
@@ -131,6 +131,9 @@ export function buildAuthorizationUrl(
|
|
|
131
131
|
state: string;
|
|
132
132
|
nonce: string;
|
|
133
133
|
codeChallenge: string;
|
|
134
|
+
/** OIDC prompt ('none' = silent: the OP answers from its session or
|
|
135
|
+
* errors login_required — never an interaction). */
|
|
136
|
+
prompt?: string;
|
|
134
137
|
},
|
|
135
138
|
): string {
|
|
136
139
|
const url = new URL(metadata.authorization_endpoint);
|
|
@@ -142,6 +145,7 @@ export function buildAuthorizationUrl(
|
|
|
142
145
|
url.searchParams.set("nonce", params.nonce);
|
|
143
146
|
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
144
147
|
url.searchParams.set("code_challenge_method", "S256");
|
|
148
|
+
if (params.prompt) url.searchParams.set("prompt", params.prompt);
|
|
145
149
|
return url.toString();
|
|
146
150
|
}
|
|
147
151
|
|