@konneal/engine 0.2.91 → 0.2.92
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-PUU6HHRZ.js → ask-UG5UYRJV.js} +2 -2
- package/dist/{chunk-ZIDDK32O.js → chunk-EHABW23I.js} +9 -340
- package/dist/{chunk-JHATKAWW.js → chunk-QBWZ6HVP.js} +442 -1
- package/dist/{tools-4NFFHVTW.js → tools-PI7IXAL4.js} +1 -1
- package/dist/tools-content.d.ts +2 -0
- package/dist/worker_public/src/index.js +4 -4
- package/package.json +1 -1
- package/workers/worker_public/src/tools-content.ts +121 -1
- package/workers/worker_public/src/tools.ts +2 -2
|
@@ -2,6 +2,8 @@ import {
|
|
|
2
2
|
TOOL_DECLARATION,
|
|
3
3
|
bindModelNode,
|
|
4
4
|
contractV2,
|
|
5
|
+
evaluate,
|
|
6
|
+
evaluateConditionSets,
|
|
5
7
|
licenseBoundaryNote,
|
|
6
8
|
licenseBoundaryRefusal,
|
|
7
9
|
licensedEntryForPackage,
|
|
@@ -9,12 +11,14 @@ import {
|
|
|
9
11
|
modelEcho,
|
|
10
12
|
modelGroundingBlock,
|
|
11
13
|
parseToolCall,
|
|
14
|
+
quantitiesIn,
|
|
12
15
|
resolveBlocks,
|
|
13
16
|
runTool,
|
|
14
17
|
standardForDocNumber,
|
|
15
18
|
tableRetyped,
|
|
16
|
-
toolNote
|
|
17
|
-
|
|
19
|
+
toolNote,
|
|
20
|
+
verdictNote
|
|
21
|
+
} from "./chunk-QBWZ6HVP.js";
|
|
18
22
|
import {
|
|
19
23
|
NO_CONTEXT,
|
|
20
24
|
answerQuality,
|
|
@@ -436,343 +440,8 @@ async function completeFigures(db, answer, alreadyAttached, used = []) {
|
|
|
436
440
|
return figs;
|
|
437
441
|
}
|
|
438
442
|
|
|
439
|
-
// workers/worker_public/src/verdict.ts
|
|
440
|
-
function tokenize(src) {
|
|
441
|
-
const toks = [];
|
|
442
|
-
let i = 0;
|
|
443
|
-
const s = src.replace(/\s+/g, " ");
|
|
444
|
-
while (i < s.length) {
|
|
445
|
-
const c = s[i];
|
|
446
|
-
if (c === " ") {
|
|
447
|
-
i++;
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
if (/[0-9.]/.test(c)) {
|
|
451
|
-
const m = s.slice(i).match(/^[0-9]*\.?[0-9]+/);
|
|
452
|
-
toks.push({ t: "num", v: Number(m[0]) });
|
|
453
|
-
i += m[0].length;
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
if (/[A-Za-z_]/.test(c)) {
|
|
457
|
-
const m = s.slice(i).match(/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*/);
|
|
458
|
-
toks.push({ t: "id", v: m[0] });
|
|
459
|
-
i += m[0].length;
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
const two = s.slice(i, i + 2);
|
|
463
|
-
if ([">=", "<=", "==", "!="].includes(two)) {
|
|
464
|
-
toks.push({ t: "op", v: two });
|
|
465
|
-
i += 2;
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
if ("+-*/()<>".includes(c)) {
|
|
469
|
-
toks.push({ t: "op", v: c });
|
|
470
|
-
i++;
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
throw new Error(`bad char ${c}`);
|
|
474
|
-
}
|
|
475
|
-
return toks;
|
|
476
|
-
}
|
|
477
|
-
function parseAndEval(src, params) {
|
|
478
|
-
const toks = tokenize(src);
|
|
479
|
-
let p = 0;
|
|
480
|
-
const peek = () => toks[p];
|
|
481
|
-
const eat = (v) => {
|
|
482
|
-
const t = toks[p++];
|
|
483
|
-
if (v && (!t || t.t !== "op" || t.v !== v)) throw new Error(`expected ${v}`);
|
|
484
|
-
return t;
|
|
485
|
-
};
|
|
486
|
-
const isKw = (k) => {
|
|
487
|
-
const t = peek();
|
|
488
|
-
return t && t.t === "id" && t.v.toLowerCase() === k;
|
|
489
|
-
};
|
|
490
|
-
function or() {
|
|
491
|
-
let l = and();
|
|
492
|
-
while (isKw("or")) {
|
|
493
|
-
p++;
|
|
494
|
-
const r = and();
|
|
495
|
-
l = truthy(l) || truthy(r);
|
|
496
|
-
}
|
|
497
|
-
return l;
|
|
498
|
-
}
|
|
499
|
-
function and() {
|
|
500
|
-
let l = not();
|
|
501
|
-
while (isKw("and")) {
|
|
502
|
-
p++;
|
|
503
|
-
const r = not();
|
|
504
|
-
l = truthy(l) && truthy(r);
|
|
505
|
-
}
|
|
506
|
-
return l;
|
|
507
|
-
}
|
|
508
|
-
function not() {
|
|
509
|
-
if (isKw("not")) {
|
|
510
|
-
p++;
|
|
511
|
-
return !truthy(not());
|
|
512
|
-
}
|
|
513
|
-
return cmp();
|
|
514
|
-
}
|
|
515
|
-
function cmp() {
|
|
516
|
-
const l = add();
|
|
517
|
-
const t = peek();
|
|
518
|
-
if (t && t.t === "op" && [">=", "<=", ">", "<", "==", "!="].includes(t.v)) {
|
|
519
|
-
p++;
|
|
520
|
-
const r = add();
|
|
521
|
-
switch (t.v) {
|
|
522
|
-
case ">=":
|
|
523
|
-
return num3(l) >= num3(r);
|
|
524
|
-
case "<=":
|
|
525
|
-
return num3(l) <= num3(r);
|
|
526
|
-
case ">":
|
|
527
|
-
return num3(l) > num3(r);
|
|
528
|
-
case "<":
|
|
529
|
-
return num3(l) < num3(r);
|
|
530
|
-
case "==":
|
|
531
|
-
return num3(l) === num3(r);
|
|
532
|
-
default:
|
|
533
|
-
return num3(l) !== num3(r);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return l;
|
|
537
|
-
}
|
|
538
|
-
function add() {
|
|
539
|
-
let l = mul();
|
|
540
|
-
for (; ; ) {
|
|
541
|
-
const t = peek();
|
|
542
|
-
if (t && t.t === "op" && (t.v === "+" || t.v === "-")) {
|
|
543
|
-
p++;
|
|
544
|
-
const r = mul();
|
|
545
|
-
l = t.v === "+" ? num3(l) + num3(r) : num3(l) - num3(r);
|
|
546
|
-
} else return l;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
function mul() {
|
|
550
|
-
let l = unary();
|
|
551
|
-
for (; ; ) {
|
|
552
|
-
const t = peek();
|
|
553
|
-
if (t && t.t === "op" && (t.v === "*" || t.v === "/")) {
|
|
554
|
-
p++;
|
|
555
|
-
const r = unary();
|
|
556
|
-
l = t.v === "*" ? num3(l) * num3(r) : num3(l) / num3(r);
|
|
557
|
-
} else return l;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
function unary() {
|
|
561
|
-
const t = peek();
|
|
562
|
-
if (t && t.t === "op" && t.v === "-") {
|
|
563
|
-
p++;
|
|
564
|
-
return -num3(unary());
|
|
565
|
-
}
|
|
566
|
-
return atom();
|
|
567
|
-
}
|
|
568
|
-
function atom() {
|
|
569
|
-
const t = eat();
|
|
570
|
-
if (!t) throw new Error("unexpected end");
|
|
571
|
-
if (t.t === "num") return t.v;
|
|
572
|
-
if (t.t === "id") {
|
|
573
|
-
if (params[t.v] !== void 0) return params[t.v];
|
|
574
|
-
throw new Error(`missing ${t.v}`);
|
|
575
|
-
}
|
|
576
|
-
if (t.v === "(") {
|
|
577
|
-
const v = or();
|
|
578
|
-
eat(")");
|
|
579
|
-
return v;
|
|
580
|
-
}
|
|
581
|
-
throw new Error(`unexpected ${t.v}`);
|
|
582
|
-
}
|
|
583
|
-
const truthy = (v) => typeof v === "boolean" ? v : v !== 0;
|
|
584
|
-
const num3 = (v) => typeof v === "boolean" ? v ? 1 : 0 : v;
|
|
585
|
-
const out = or();
|
|
586
|
-
if (p !== toks.length) throw new Error("trailing tokens");
|
|
587
|
-
return out;
|
|
588
|
-
}
|
|
589
|
-
function oclBody(s) {
|
|
590
|
-
const m = String(s ?? "").match(/ocl\{([\s\S]*?)\}/);
|
|
591
|
-
return m ? m[1].trim() : null;
|
|
592
|
-
}
|
|
593
|
-
function extractChecks(content) {
|
|
594
|
-
if (!content || typeof content !== "object") return [];
|
|
595
|
-
const c = content;
|
|
596
|
-
const out = [];
|
|
597
|
-
const push = (e) => {
|
|
598
|
-
const b = e && oclBody(e);
|
|
599
|
-
if (b) out.push(b);
|
|
600
|
-
};
|
|
601
|
-
push(c.check);
|
|
602
|
-
push(c.limit?.expression);
|
|
603
|
-
push(c.acceptance_criteria?.limit && !c.acceptance_criteria.limit.expression?.includes("ocl{") ? null : c.acceptance_criteria?.limit?.expression);
|
|
604
|
-
const st = c.acceptance_criteria?.limit;
|
|
605
|
-
if (st?.expression && st.operator && st.threshold_expression) {
|
|
606
|
-
out.push(`${st.expression} ${st.operator} ${st.threshold_expression}`);
|
|
607
|
-
}
|
|
608
|
-
return [...new Set(out)];
|
|
609
|
-
}
|
|
610
|
-
function symbolsIn(checks) {
|
|
611
|
-
const ids = /* @__PURE__ */ new Set();
|
|
612
|
-
const KEYWORDS = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
613
|
-
for (const chk of checks) {
|
|
614
|
-
try {
|
|
615
|
-
for (const t of tokenize(chk)) if (t.t === "id" && !KEYWORDS.has(t.v.toLowerCase())) ids.add(t.v);
|
|
616
|
-
} catch {
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return [...ids];
|
|
620
|
-
}
|
|
621
|
-
function parseNumber(raw) {
|
|
622
|
-
let s = raw.replace(/[ ,]/g, "");
|
|
623
|
-
s = s.replace(/\.(\d{3})$/, "$1");
|
|
624
|
-
return Number(s.replace(/,(?=\d{3}\b)/g, ""));
|
|
625
|
-
}
|
|
626
|
-
function extractParams(query, symbols) {
|
|
627
|
-
const params = {};
|
|
628
|
-
for (const sym of symbols) {
|
|
629
|
-
const leaf = sym.split(".").pop() ?? sym;
|
|
630
|
-
const esc = leaf.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
631
|
-
const re = new RegExp(`\\b${esc}\\b\\D{0,14}?([0-9][0-9 ,.]*[0-9])`, "iu");
|
|
632
|
-
const m = query.match(re);
|
|
633
|
-
if (m) {
|
|
634
|
-
const v = parseNumber(m[1]);
|
|
635
|
-
if (Number.isFinite(v)) params[sym] = v;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return params;
|
|
639
|
-
}
|
|
640
|
-
function evaluate(content, query) {
|
|
641
|
-
const c = content && typeof content === "object" ? content : {};
|
|
642
|
-
const checks = extractChecks(content);
|
|
643
|
-
if (!checks.length) return null;
|
|
644
|
-
const symbols = symbolsIn(checks);
|
|
645
|
-
const params = extractParams(query, symbols);
|
|
646
|
-
const missing = symbols.filter((s) => params[s] === void 0);
|
|
647
|
-
const machine = checks.map((expression) => {
|
|
648
|
-
const values = {};
|
|
649
|
-
try {
|
|
650
|
-
for (const t of tokenize(expression)) if (t.t === "id" && params[t.v] !== void 0) values[t.v] = params[t.v];
|
|
651
|
-
} catch {
|
|
652
|
-
}
|
|
653
|
-
let result = null;
|
|
654
|
-
if (missing.length === 0) {
|
|
655
|
-
try {
|
|
656
|
-
result = !!parseAndEval(expression, params);
|
|
657
|
-
} catch {
|
|
658
|
-
result = null;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
return { expression, symbolic: expression, values, result };
|
|
662
|
-
});
|
|
663
|
-
if (missing.length) {
|
|
664
|
-
return { verdict: "void", missing, checks: machine };
|
|
665
|
-
}
|
|
666
|
-
const failed = machine.some((m) => m.result === false);
|
|
667
|
-
const evaluable = machine.some((m) => m.result !== null);
|
|
668
|
-
if (!evaluable) return null;
|
|
669
|
-
return {
|
|
670
|
-
verdict: failed ? "fail" : "pass",
|
|
671
|
-
on_violation: failed ? String(c.on_violation ?? "invalid") : void 0,
|
|
672
|
-
violation_meaning: failed ? typeof c.violation_meaning === "string" ? c.violation_meaning : void 0 : void 0,
|
|
673
|
-
missing: [],
|
|
674
|
-
checks: machine
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
function verdictNote(v, node) {
|
|
678
|
-
const lines = [
|
|
679
|
-
`Machine verdict (deterministic evaluation of node ${node.node_id}${node.clause?.urn ? `, ${node.clause.urn}` : ""}) \u2014 the service EXECUTED the node's machine check against the values stated in the question:`
|
|
680
|
-
];
|
|
681
|
-
for (const c of v.checks) {
|
|
682
|
-
const vals = Object.entries(c.values).map(([k, n]) => `${k}=${n}`).join(", ");
|
|
683
|
-
lines.push(`- ${c.expression}${vals ? ` [${vals}]` : ""} \u2192 ${c.result === null ? "not evaluated" : c.result ? "holds" : "VIOLATED"}`);
|
|
684
|
-
}
|
|
685
|
-
if (v.verdict === "void") {
|
|
686
|
-
lines.push(`VERDICT: VOID \u2014 the question does not state: ${v.missing.join(", ")}. Say exactly what is missing; never assume values.`);
|
|
687
|
-
} else if (v.verdict === "pass") {
|
|
688
|
-
lines.push(`VERDICT: PASS \u2014 every machine check holds at the stated values. Present this verdict, the arithmetic above, and cite the node's clause.`);
|
|
689
|
-
} else {
|
|
690
|
-
lines.push(`VERDICT: ${String(v.on_violation ?? "FAIL").toUpperCase()} \u2014 a machine check is violated. Present this verdict, the arithmetic, the violation meaning verbatim, and cite the node's clause.`);
|
|
691
|
-
}
|
|
692
|
-
lines.push("This verdict is computed data \u2014 quote it faithfully; do not recompute, soften, or contradict it.");
|
|
693
|
-
return lines.join("\n");
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// workers/worker_public/src/conditions.ts
|
|
697
|
-
var NUM = String.raw`-?\d+(?:[.,]\d+)?`;
|
|
698
|
-
function quantitiesIn(query) {
|
|
699
|
-
const out = {};
|
|
700
|
-
const num3 = (s) => Number(s.replace(",", "."));
|
|
701
|
-
const put = (kind, stated, stated_unit, si) => {
|
|
702
|
-
if (Number.isFinite(si)) out[kind] = { stated, stated_unit, si };
|
|
703
|
-
};
|
|
704
|
-
const tempC = query.match(new RegExp(`(${NUM})\\s*(?:\xB0\\s*)?C\\b`));
|
|
705
|
-
if (tempC) put("temperature", num3(tempC[1]), "degC", num3(tempC[1]) + 273.15);
|
|
706
|
-
const tempK = query.match(new RegExp(`(${NUM})\\s*K\\b`));
|
|
707
|
-
if (tempK && out.temperature === void 0) put("temperature", num3(tempK[1]), "K", num3(tempK[1]));
|
|
708
|
-
const rh = query.match(new RegExp(`(${NUM})\\s*%\\s*(?:RH\\b|relative\\s+humidity)?`, "i"));
|
|
709
|
-
if (rh) put("relative_humidity", num3(rh[1]), "%", num3(rh[1]) / 100);
|
|
710
|
-
const hours = query.match(new RegExp(`(${NUM})\\s*h\\b`, "i"));
|
|
711
|
-
if (hours) put("duration", num3(hours[1]), "h", num3(hours[1]) * 3600);
|
|
712
|
-
const days = query.match(new RegExp(`(${NUM})\\s*days?\\b`, "i"));
|
|
713
|
-
if (days && out.duration === void 0) put("duration", num3(days[1]), "d", num3(days[1]) * 86400);
|
|
714
|
-
return out;
|
|
715
|
-
}
|
|
716
|
-
function scoreSet(entries, q) {
|
|
717
|
-
const checks = [];
|
|
718
|
-
let distance = 0;
|
|
719
|
-
let stated = 0;
|
|
720
|
-
for (const e of entries) {
|
|
721
|
-
const siEntry = e;
|
|
722
|
-
const statedQ = q[e.quantity_kind];
|
|
723
|
-
if (!statedQ || !siEntry?.si) continue;
|
|
724
|
-
const tol = Number(String(siEntry.tolerance ?? "0").replace(",", "."));
|
|
725
|
-
const tolSi = (Number.isFinite(tol) ? tol : 0) * (siEntry.si.unit === "K" ? 1 : siEntry.si.unit === "1" ? 0.01 : 1);
|
|
726
|
-
const in_band = statedQ.si >= siEntry.si.value - tolSi && statedQ.si <= siEntry.si.value + tolSi;
|
|
727
|
-
const gap = Math.max(0, statedQ.si - (siEntry.si.value + tolSi), siEntry.si.value - tolSi - statedQ.si);
|
|
728
|
-
distance += gap / Math.max(tolSi, 1);
|
|
729
|
-
stated += 1;
|
|
730
|
-
checks.push({
|
|
731
|
-
quantity_kind: e.quantity_kind,
|
|
732
|
-
band: `${siEntry.si.value} ${siEntry.si.unit} \xB1${tolSi}`,
|
|
733
|
-
stated: statedQ.stated,
|
|
734
|
-
stated_unit: statedQ.stated_unit,
|
|
735
|
-
in_band
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
return stated ? { checks, distance, stated } : null;
|
|
739
|
-
}
|
|
740
|
-
function evaluateConditionSets(nodes, query) {
|
|
741
|
-
const q = quantitiesIn(query);
|
|
742
|
-
const kinds = Object.keys(q);
|
|
743
|
-
if (!kinds.length) return null;
|
|
744
|
-
const scored = [];
|
|
745
|
-
for (const n of nodes) {
|
|
746
|
-
const c = n.content && typeof n.content === "object" ? n.content : {};
|
|
747
|
-
const payload = c.payload ?? {};
|
|
748
|
-
const entries = Array.isArray(payload.entries) ? payload.entries : [];
|
|
749
|
-
if (!entries.length) continue;
|
|
750
|
-
const s = scoreSet(entries, q);
|
|
751
|
-
if (s) scored.push({ node_id: n.node_id, checks: s.checks, distance: s.distance });
|
|
752
|
-
}
|
|
753
|
-
if (!scored.length) return null;
|
|
754
|
-
const matched = scored.filter((s) => s.checks.every((c) => c.in_band));
|
|
755
|
-
scored.sort((a, b) => a.distance - b.distance);
|
|
756
|
-
if (matched.length) {
|
|
757
|
-
return {
|
|
758
|
-
verdict: "pass",
|
|
759
|
-
matched: matched.map((m) => m.node_id),
|
|
760
|
-
checks: matched[0].checks,
|
|
761
|
-
note: `VERDICT: PASS \u2014 the stated combination (${kinds.join(", ")}) matches a severity set (${matched[0].checks.map((c) => c.band).join("; ")}). Present this verdict and cite the set's clause. The machine set identifier rides the verdict block as data \u2014 never write it in your prose.`
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
const nearest = scored[0];
|
|
765
|
-
return {
|
|
766
|
-
verdict: "fail",
|
|
767
|
-
matched: [],
|
|
768
|
-
nearest: { node_id: nearest.node_id, distance: Number(nearest.distance.toFixed(2)), bands: nearest.checks.map((c) => c.band) },
|
|
769
|
-
checks: nearest.checks,
|
|
770
|
-
note: `VERDICT: FAIL \u2014 no severity set admits the stated combination. The nearest set (bands: ${nearest.checks.map((c) => c.band).join("; ")}) is the closest match. Say the combination is outside the menu and describe the nearest set's bands; never soften it. The machine set identifier rides the verdict block as data \u2014 never write it in your prose.`
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
|
|
774
443
|
// workers/worker_public/src/aggregation.ts
|
|
775
|
-
var
|
|
444
|
+
var NUM = String.raw`-?\d+(?:[,\s]?\d{3})*(?:[.,]\d+)?`;
|
|
776
445
|
var UNIT_WORDS = {
|
|
777
446
|
kg: ["mass", "load", "weight"],
|
|
778
447
|
s: ["time", "duration", "second"],
|
|
@@ -836,7 +505,7 @@ function intervalPairs(cols) {
|
|
|
836
505
|
}
|
|
837
506
|
function statedInInterval(query, unit) {
|
|
838
507
|
if (!unit) return null;
|
|
839
|
-
const re = new RegExp(`(${
|
|
508
|
+
const re = new RegExp(`(${NUM})\\s*${unit.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}\\b`, "i");
|
|
840
509
|
const m = query.match(re);
|
|
841
510
|
return m ? num2(m[1]) : null;
|
|
842
511
|
}
|
|
@@ -871,7 +540,7 @@ function classValueExists(rows, cols, token) {
|
|
|
871
540
|
}
|
|
872
541
|
function statedUnits(query) {
|
|
873
542
|
const out = /* @__PURE__ */ new Set();
|
|
874
|
-
const re = new RegExp(`(${
|
|
543
|
+
const re = new RegExp(`(${NUM})\\s*([%\xB0a-zA-Z][a-zA-Z/.%\xB0]*\\b)`, "g");
|
|
875
544
|
for (const m of query.matchAll(re)) {
|
|
876
545
|
if (m[2]) out.add(m[2].replace("\u2062", "").trim());
|
|
877
546
|
}
|
|
@@ -241,6 +241,341 @@ function modelEcho(node) {
|
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
// workers/worker_public/src/verdict.ts
|
|
245
|
+
function tokenize(src) {
|
|
246
|
+
const toks = [];
|
|
247
|
+
let i = 0;
|
|
248
|
+
const s = src.replace(/\s+/g, " ");
|
|
249
|
+
while (i < s.length) {
|
|
250
|
+
const c = s[i];
|
|
251
|
+
if (c === " ") {
|
|
252
|
+
i++;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (/[0-9.]/.test(c)) {
|
|
256
|
+
const m = s.slice(i).match(/^[0-9]*\.?[0-9]+/);
|
|
257
|
+
toks.push({ t: "num", v: Number(m[0]) });
|
|
258
|
+
i += m[0].length;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (/[A-Za-z_]/.test(c)) {
|
|
262
|
+
const m = s.slice(i).match(/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*/);
|
|
263
|
+
toks.push({ t: "id", v: m[0] });
|
|
264
|
+
i += m[0].length;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const two = s.slice(i, i + 2);
|
|
268
|
+
if ([">=", "<=", "==", "!="].includes(two)) {
|
|
269
|
+
toks.push({ t: "op", v: two });
|
|
270
|
+
i += 2;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if ("+-*/()<>".includes(c)) {
|
|
274
|
+
toks.push({ t: "op", v: c });
|
|
275
|
+
i++;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
throw new Error(`bad char ${c}`);
|
|
279
|
+
}
|
|
280
|
+
return toks;
|
|
281
|
+
}
|
|
282
|
+
function parseAndEval(src, params) {
|
|
283
|
+
const toks = tokenize(src);
|
|
284
|
+
let p = 0;
|
|
285
|
+
const peek = () => toks[p];
|
|
286
|
+
const eat = (v) => {
|
|
287
|
+
const t = toks[p++];
|
|
288
|
+
if (v && (!t || t.t !== "op" || t.v !== v)) throw new Error(`expected ${v}`);
|
|
289
|
+
return t;
|
|
290
|
+
};
|
|
291
|
+
const isKw = (k) => {
|
|
292
|
+
const t = peek();
|
|
293
|
+
return t && t.t === "id" && t.v.toLowerCase() === k;
|
|
294
|
+
};
|
|
295
|
+
function or() {
|
|
296
|
+
let l = and();
|
|
297
|
+
while (isKw("or")) {
|
|
298
|
+
p++;
|
|
299
|
+
const r = and();
|
|
300
|
+
l = truthy(l) || truthy(r);
|
|
301
|
+
}
|
|
302
|
+
return l;
|
|
303
|
+
}
|
|
304
|
+
function and() {
|
|
305
|
+
let l = not();
|
|
306
|
+
while (isKw("and")) {
|
|
307
|
+
p++;
|
|
308
|
+
const r = not();
|
|
309
|
+
l = truthy(l) && truthy(r);
|
|
310
|
+
}
|
|
311
|
+
return l;
|
|
312
|
+
}
|
|
313
|
+
function not() {
|
|
314
|
+
if (isKw("not")) {
|
|
315
|
+
p++;
|
|
316
|
+
return !truthy(not());
|
|
317
|
+
}
|
|
318
|
+
return cmp();
|
|
319
|
+
}
|
|
320
|
+
function cmp() {
|
|
321
|
+
const l = add();
|
|
322
|
+
const t = peek();
|
|
323
|
+
if (t && t.t === "op" && [">=", "<=", ">", "<", "==", "!="].includes(t.v)) {
|
|
324
|
+
p++;
|
|
325
|
+
const r = add();
|
|
326
|
+
switch (t.v) {
|
|
327
|
+
case ">=":
|
|
328
|
+
return num(l) >= num(r);
|
|
329
|
+
case "<=":
|
|
330
|
+
return num(l) <= num(r);
|
|
331
|
+
case ">":
|
|
332
|
+
return num(l) > num(r);
|
|
333
|
+
case "<":
|
|
334
|
+
return num(l) < num(r);
|
|
335
|
+
case "==":
|
|
336
|
+
return num(l) === num(r);
|
|
337
|
+
default:
|
|
338
|
+
return num(l) !== num(r);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return l;
|
|
342
|
+
}
|
|
343
|
+
function add() {
|
|
344
|
+
let l = mul();
|
|
345
|
+
for (; ; ) {
|
|
346
|
+
const t = peek();
|
|
347
|
+
if (t && t.t === "op" && (t.v === "+" || t.v === "-")) {
|
|
348
|
+
p++;
|
|
349
|
+
const r = mul();
|
|
350
|
+
l = t.v === "+" ? num(l) + num(r) : num(l) - num(r);
|
|
351
|
+
} else return l;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function mul() {
|
|
355
|
+
let l = unary();
|
|
356
|
+
for (; ; ) {
|
|
357
|
+
const t = peek();
|
|
358
|
+
if (t && t.t === "op" && (t.v === "*" || t.v === "/")) {
|
|
359
|
+
p++;
|
|
360
|
+
const r = unary();
|
|
361
|
+
l = t.v === "*" ? num(l) * num(r) : num(l) / num(r);
|
|
362
|
+
} else return l;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function unary() {
|
|
366
|
+
const t = peek();
|
|
367
|
+
if (t && t.t === "op" && t.v === "-") {
|
|
368
|
+
p++;
|
|
369
|
+
return -num(unary());
|
|
370
|
+
}
|
|
371
|
+
return atom();
|
|
372
|
+
}
|
|
373
|
+
function atom() {
|
|
374
|
+
const t = eat();
|
|
375
|
+
if (!t) throw new Error("unexpected end");
|
|
376
|
+
if (t.t === "num") return t.v;
|
|
377
|
+
if (t.t === "id") {
|
|
378
|
+
if (params[t.v] !== void 0) return params[t.v];
|
|
379
|
+
throw new Error(`missing ${t.v}`);
|
|
380
|
+
}
|
|
381
|
+
if (t.v === "(") {
|
|
382
|
+
const v = or();
|
|
383
|
+
eat(")");
|
|
384
|
+
return v;
|
|
385
|
+
}
|
|
386
|
+
throw new Error(`unexpected ${t.v}`);
|
|
387
|
+
}
|
|
388
|
+
const truthy = (v) => typeof v === "boolean" ? v : v !== 0;
|
|
389
|
+
const num = (v) => typeof v === "boolean" ? v ? 1 : 0 : v;
|
|
390
|
+
const out = or();
|
|
391
|
+
if (p !== toks.length) throw new Error("trailing tokens");
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
function oclBody(s) {
|
|
395
|
+
const m = String(s ?? "").match(/ocl\{([\s\S]*?)\}/);
|
|
396
|
+
return m ? m[1].trim() : null;
|
|
397
|
+
}
|
|
398
|
+
function extractChecks(content) {
|
|
399
|
+
if (!content || typeof content !== "object") return [];
|
|
400
|
+
const c = content;
|
|
401
|
+
const out = [];
|
|
402
|
+
const push = (e) => {
|
|
403
|
+
const b = e && oclBody(e);
|
|
404
|
+
if (b) out.push(b);
|
|
405
|
+
};
|
|
406
|
+
push(c.check);
|
|
407
|
+
push(c.limit?.expression);
|
|
408
|
+
push(c.acceptance_criteria?.limit && !c.acceptance_criteria.limit.expression?.includes("ocl{") ? null : c.acceptance_criteria?.limit?.expression);
|
|
409
|
+
const st = c.acceptance_criteria?.limit;
|
|
410
|
+
if (st?.expression && st.operator && st.threshold_expression) {
|
|
411
|
+
out.push(`${st.expression} ${st.operator} ${st.threshold_expression}`);
|
|
412
|
+
}
|
|
413
|
+
return [...new Set(out)];
|
|
414
|
+
}
|
|
415
|
+
function symbolsIn(checks) {
|
|
416
|
+
const ids = /* @__PURE__ */ new Set();
|
|
417
|
+
const KEYWORDS = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
418
|
+
for (const chk of checks) {
|
|
419
|
+
try {
|
|
420
|
+
for (const t of tokenize(chk)) if (t.t === "id" && !KEYWORDS.has(t.v.toLowerCase())) ids.add(t.v);
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return [...ids];
|
|
425
|
+
}
|
|
426
|
+
function parseNumber(raw) {
|
|
427
|
+
let s = raw.replace(/[ ,]/g, "");
|
|
428
|
+
s = s.replace(/\.(\d{3})$/, "$1");
|
|
429
|
+
return Number(s.replace(/,(?=\d{3}\b)/g, ""));
|
|
430
|
+
}
|
|
431
|
+
function extractParams(query, symbols) {
|
|
432
|
+
const params = {};
|
|
433
|
+
for (const sym of symbols) {
|
|
434
|
+
const leaf = sym.split(".").pop() ?? sym;
|
|
435
|
+
const esc = leaf.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
436
|
+
const re = new RegExp(`\\b${esc}\\b\\D{0,14}?([0-9][0-9 ,.]*[0-9])`, "iu");
|
|
437
|
+
const m = query.match(re);
|
|
438
|
+
if (m) {
|
|
439
|
+
const v = parseNumber(m[1]);
|
|
440
|
+
if (Number.isFinite(v)) params[sym] = v;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return params;
|
|
444
|
+
}
|
|
445
|
+
function evaluate(content, query) {
|
|
446
|
+
const c = content && typeof content === "object" ? content : {};
|
|
447
|
+
const checks = extractChecks(content);
|
|
448
|
+
if (!checks.length) return null;
|
|
449
|
+
const symbols = symbolsIn(checks);
|
|
450
|
+
const params = extractParams(query, symbols);
|
|
451
|
+
const missing = symbols.filter((s) => params[s] === void 0);
|
|
452
|
+
const machine = checks.map((expression) => {
|
|
453
|
+
const values = {};
|
|
454
|
+
try {
|
|
455
|
+
for (const t of tokenize(expression)) if (t.t === "id" && params[t.v] !== void 0) values[t.v] = params[t.v];
|
|
456
|
+
} catch {
|
|
457
|
+
}
|
|
458
|
+
let result = null;
|
|
459
|
+
if (missing.length === 0) {
|
|
460
|
+
try {
|
|
461
|
+
result = !!parseAndEval(expression, params);
|
|
462
|
+
} catch {
|
|
463
|
+
result = null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { expression, symbolic: expression, values, result };
|
|
467
|
+
});
|
|
468
|
+
if (missing.length) {
|
|
469
|
+
return { verdict: "void", missing, checks: machine };
|
|
470
|
+
}
|
|
471
|
+
const failed = machine.some((m) => m.result === false);
|
|
472
|
+
const evaluable = machine.some((m) => m.result !== null);
|
|
473
|
+
if (!evaluable) return null;
|
|
474
|
+
return {
|
|
475
|
+
verdict: failed ? "fail" : "pass",
|
|
476
|
+
on_violation: failed ? String(c.on_violation ?? "invalid") : void 0,
|
|
477
|
+
violation_meaning: failed ? typeof c.violation_meaning === "string" ? c.violation_meaning : void 0 : void 0,
|
|
478
|
+
missing: [],
|
|
479
|
+
checks: machine
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function verdictNote(v, node) {
|
|
483
|
+
const lines = [
|
|
484
|
+
`Machine verdict (deterministic evaluation of node ${node.node_id}${node.clause?.urn ? `, ${node.clause.urn}` : ""}) \u2014 the service EXECUTED the node's machine check against the values stated in the question:`
|
|
485
|
+
];
|
|
486
|
+
for (const c of v.checks) {
|
|
487
|
+
const vals = Object.entries(c.values).map(([k, n]) => `${k}=${n}`).join(", ");
|
|
488
|
+
lines.push(`- ${c.expression}${vals ? ` [${vals}]` : ""} \u2192 ${c.result === null ? "not evaluated" : c.result ? "holds" : "VIOLATED"}`);
|
|
489
|
+
}
|
|
490
|
+
if (v.verdict === "void") {
|
|
491
|
+
lines.push(`VERDICT: VOID \u2014 the question does not state: ${v.missing.join(", ")}. Say exactly what is missing; never assume values.`);
|
|
492
|
+
} else if (v.verdict === "pass") {
|
|
493
|
+
lines.push(`VERDICT: PASS \u2014 every machine check holds at the stated values. Present this verdict, the arithmetic above, and cite the node's clause.`);
|
|
494
|
+
} else {
|
|
495
|
+
lines.push(`VERDICT: ${String(v.on_violation ?? "FAIL").toUpperCase()} \u2014 a machine check is violated. Present this verdict, the arithmetic, the violation meaning verbatim, and cite the node's clause.`);
|
|
496
|
+
}
|
|
497
|
+
lines.push("This verdict is computed data \u2014 quote it faithfully; do not recompute, soften, or contradict it.");
|
|
498
|
+
return lines.join("\n");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// workers/worker_public/src/conditions.ts
|
|
502
|
+
var NUM = String.raw`-?\d+(?:[.,]\d+)?`;
|
|
503
|
+
function quantitiesIn(query) {
|
|
504
|
+
const out = {};
|
|
505
|
+
const num = (s) => Number(s.replace(",", "."));
|
|
506
|
+
const put = (kind, stated, stated_unit, si) => {
|
|
507
|
+
if (Number.isFinite(si)) out[kind] = { stated, stated_unit, si };
|
|
508
|
+
};
|
|
509
|
+
const tempC = query.match(new RegExp(`(${NUM})\\s*(?:\xB0\\s*)?C\\b`));
|
|
510
|
+
if (tempC) put("temperature", num(tempC[1]), "degC", num(tempC[1]) + 273.15);
|
|
511
|
+
const tempK = query.match(new RegExp(`(${NUM})\\s*K\\b`));
|
|
512
|
+
if (tempK && out.temperature === void 0) put("temperature", num(tempK[1]), "K", num(tempK[1]));
|
|
513
|
+
const rh = query.match(new RegExp(`(${NUM})\\s*%\\s*(?:RH\\b|relative\\s+humidity)?`, "i"));
|
|
514
|
+
if (rh) put("relative_humidity", num(rh[1]), "%", num(rh[1]) / 100);
|
|
515
|
+
const hours = query.match(new RegExp(`(${NUM})\\s*h\\b`, "i"));
|
|
516
|
+
if (hours) put("duration", num(hours[1]), "h", num(hours[1]) * 3600);
|
|
517
|
+
const days = query.match(new RegExp(`(${NUM})\\s*days?\\b`, "i"));
|
|
518
|
+
if (days && out.duration === void 0) put("duration", num(days[1]), "d", num(days[1]) * 86400);
|
|
519
|
+
return out;
|
|
520
|
+
}
|
|
521
|
+
function scoreSet(entries, q) {
|
|
522
|
+
const checks = [];
|
|
523
|
+
let distance = 0;
|
|
524
|
+
let stated = 0;
|
|
525
|
+
for (const e of entries) {
|
|
526
|
+
const siEntry = e;
|
|
527
|
+
const statedQ = q[e.quantity_kind];
|
|
528
|
+
if (!statedQ || !siEntry?.si) continue;
|
|
529
|
+
const tol = Number(String(siEntry.tolerance ?? "0").replace(",", "."));
|
|
530
|
+
const tolSi = (Number.isFinite(tol) ? tol : 0) * (siEntry.si.unit === "K" ? 1 : siEntry.si.unit === "1" ? 0.01 : 1);
|
|
531
|
+
const in_band = statedQ.si >= siEntry.si.value - tolSi && statedQ.si <= siEntry.si.value + tolSi;
|
|
532
|
+
const gap = Math.max(0, statedQ.si - (siEntry.si.value + tolSi), siEntry.si.value - tolSi - statedQ.si);
|
|
533
|
+
distance += gap / Math.max(tolSi, 1);
|
|
534
|
+
stated += 1;
|
|
535
|
+
checks.push({
|
|
536
|
+
quantity_kind: e.quantity_kind,
|
|
537
|
+
band: `${siEntry.si.value} ${siEntry.si.unit} \xB1${tolSi}`,
|
|
538
|
+
stated: statedQ.stated,
|
|
539
|
+
stated_unit: statedQ.stated_unit,
|
|
540
|
+
in_band
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
return stated ? { checks, distance, stated } : null;
|
|
544
|
+
}
|
|
545
|
+
function evaluateConditionSets(nodes, query) {
|
|
546
|
+
const q = quantitiesIn(query);
|
|
547
|
+
const kinds = Object.keys(q);
|
|
548
|
+
if (!kinds.length) return null;
|
|
549
|
+
const scored = [];
|
|
550
|
+
for (const n of nodes) {
|
|
551
|
+
const c = n.content && typeof n.content === "object" ? n.content : {};
|
|
552
|
+
const payload = c.payload ?? {};
|
|
553
|
+
const entries = Array.isArray(payload.entries) ? payload.entries : [];
|
|
554
|
+
if (!entries.length) continue;
|
|
555
|
+
const s = scoreSet(entries, q);
|
|
556
|
+
if (s) scored.push({ node_id: n.node_id, checks: s.checks, distance: s.distance });
|
|
557
|
+
}
|
|
558
|
+
if (!scored.length) return null;
|
|
559
|
+
const matched = scored.filter((s) => s.checks.every((c) => c.in_band));
|
|
560
|
+
scored.sort((a, b) => a.distance - b.distance);
|
|
561
|
+
if (matched.length) {
|
|
562
|
+
return {
|
|
563
|
+
verdict: "pass",
|
|
564
|
+
matched: matched.map((m) => m.node_id),
|
|
565
|
+
checks: matched[0].checks,
|
|
566
|
+
note: `VERDICT: PASS \u2014 the stated combination (${kinds.join(", ")}) matches a severity set (${matched[0].checks.map((c) => c.band).join("; ")}). Present this verdict and cite the set's clause. The machine set identifier rides the verdict block as data \u2014 never write it in your prose.`
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
const nearest = scored[0];
|
|
570
|
+
return {
|
|
571
|
+
verdict: "fail",
|
|
572
|
+
matched: [],
|
|
573
|
+
nearest: { node_id: nearest.node_id, distance: Number(nearest.distance.toFixed(2)), bands: nearest.checks.map((c) => c.band) },
|
|
574
|
+
checks: nearest.checks,
|
|
575
|
+
note: `VERDICT: FAIL \u2014 no severity set admits the stated combination. The nearest set (bands: ${nearest.checks.map((c) => c.band).join("; ")}) is the closest match. Say the combination is outside the menu and describe the nearest set's bands; never soften it. The machine set identifier rides the verdict block as data \u2014 never write it in your prose.`
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
244
579
|
// workers/worker_public/src/tools-content.ts
|
|
245
580
|
var slug = (docidentifier) => docidentifier.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
246
581
|
var unitsGet = {
|
|
@@ -414,6 +749,108 @@ var licensedSection = {
|
|
|
414
749
|
}
|
|
415
750
|
}
|
|
416
751
|
};
|
|
752
|
+
var verdictEvaluate = {
|
|
753
|
+
name: "verdict.evaluate",
|
|
754
|
+
description: "Deterministically evaluate a model-plane node's machine checks (OCL boolean expressions, threshold limits) against the quantities a question states. Returns pass/fail with every check's expression and values, or void with the missing parameter names when the question does not state enough. The machine computes; cite the node's clause.",
|
|
755
|
+
params: [
|
|
756
|
+
{ key: "node_id", required: true, description: "the model-plane node id, e.g. /req/metrological/repeatability (node ids appear in answers' verdict blocks)" },
|
|
757
|
+
{ key: "question", required: true, description: "the statement carrying the quantities, e.g. 'is mpe 0.02 with n_lc 3000 within the limit?'" }
|
|
758
|
+
],
|
|
759
|
+
audiences: ["mcp"],
|
|
760
|
+
handler: async (env, args) => {
|
|
761
|
+
const node_id = String(args?.node_id ?? "").trim();
|
|
762
|
+
const question = String(args?.question ?? "").trim();
|
|
763
|
+
if (!node_id.startsWith("/") || question.length < 4) return null;
|
|
764
|
+
try {
|
|
765
|
+
const rows = await env.DB.prepare(
|
|
766
|
+
"SELECT standard, kind, name, content FROM model_nodes WHERE node_id = ?1 LIMIT 2"
|
|
767
|
+
).bind(node_id).all();
|
|
768
|
+
const found = rows.results ?? [];
|
|
769
|
+
if (found.length !== 1) {
|
|
770
|
+
return {
|
|
771
|
+
name: "verdict.evaluate",
|
|
772
|
+
query: node_id,
|
|
773
|
+
output: found.length ? `The node id ${node_id} is indexed under several standards \u2014 name the standard to disambiguate. State this plainly.` : `No model-plane node ${node_id} is indexed. State this plainly.`
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const node = found[0];
|
|
777
|
+
const entry = licensedEntryForPackage(node.standard);
|
|
778
|
+
if (entry) {
|
|
779
|
+
return {
|
|
780
|
+
name: "verdict.evaluate",
|
|
781
|
+
query: node_id,
|
|
782
|
+
output: licenseBoundaryRefusal(entry.doc_number, null) ?? "License boundary \u2014 the node belongs to a licensed standard the caller's entitlement set does not cover."
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
let content;
|
|
786
|
+
try {
|
|
787
|
+
content = JSON.parse(node.content);
|
|
788
|
+
} catch {
|
|
789
|
+
content = null;
|
|
790
|
+
}
|
|
791
|
+
const v = evaluate(content, question);
|
|
792
|
+
if (!v) {
|
|
793
|
+
return {
|
|
794
|
+
name: "verdict.evaluate",
|
|
795
|
+
query: node_id,
|
|
796
|
+
output: `The node ${node_id} (${node.kind}) carries no machine-checkable expressions \u2014 it is not evaluatable by the verdict engine. State this plainly.`
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
return {
|
|
800
|
+
name: "verdict.evaluate",
|
|
801
|
+
query: node_id,
|
|
802
|
+
output: JSON.stringify({ standard: node.standard, node_id, kind: node.kind, name: node.name, verdict: v.verdict, on_violation: v.on_violation, missing: v.missing, checks: v.checks })
|
|
803
|
+
};
|
|
804
|
+
} catch {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
var conditionsCheck = {
|
|
810
|
+
name: "conditions.check",
|
|
811
|
+
description: "Check a stated combination of environmental quantities (temperature, humidity, duration, cycles) against the indexed severity/condition sets \u2014 the pass answer names the matched set and every band, the fail names the nearest set and its distance. Machine evaluation over the condition-set nodes; cite the matched set's clause.",
|
|
812
|
+
params: [
|
|
813
|
+
{ key: "question", required: true, description: "the stated combination, e.g. 'damp heat cyclic test at 55 \xB0C for 2 cycles of 24 h'" },
|
|
814
|
+
{ key: "standard", required: false, description: "optional scope, the package id, e.g. iec-60068-2-30 (defaults to every indexed condition set the caller may see)" },
|
|
815
|
+
{ key: "licensed_standards", required: false, description: "license entitlement keys for licensed condition sets, validated against the deployment's declared licenses" }
|
|
816
|
+
],
|
|
817
|
+
audiences: ["mcp"],
|
|
818
|
+
handler: async (env, args) => {
|
|
819
|
+
const question = String(args?.question ?? "").trim();
|
|
820
|
+
if (question.length < 4) return null;
|
|
821
|
+
const standard = args?.standard ? String(args.standard).trim() : null;
|
|
822
|
+
const keys = standardKeysFrom({ licensed_standards: args?.licensed_standards });
|
|
823
|
+
try {
|
|
824
|
+
const rows = standard ? await env.DB.prepare("SELECT node_id, standard, content FROM model_nodes WHERE kind = 'condition_set' AND standard = ?1").bind(standard).all() : await env.DB.prepare("SELECT node_id, standard, content FROM model_nodes WHERE kind = 'condition_set'").all();
|
|
825
|
+
const all = (rows.results ?? []).map((r) => {
|
|
826
|
+
let content = r.content;
|
|
827
|
+
try {
|
|
828
|
+
content = JSON.parse(r.content);
|
|
829
|
+
} catch {
|
|
830
|
+
}
|
|
831
|
+
return { node_id: r.node_id, standard: r.standard, content };
|
|
832
|
+
});
|
|
833
|
+
const visible = all.filter((n) => {
|
|
834
|
+
const entry = licensedEntryForPackage(n.standard);
|
|
835
|
+
return !entry || keys.has(entry.key);
|
|
836
|
+
});
|
|
837
|
+
if (!visible.length) {
|
|
838
|
+
return { name: "conditions.check", query: question, output: "No condition sets are indexed (or visible to this caller's entitlements). State this plainly." };
|
|
839
|
+
}
|
|
840
|
+
const v = evaluateConditionSets(visible, question);
|
|
841
|
+
if (!v) {
|
|
842
|
+
return { name: "conditions.check", query: question, output: "No quantities were recognized in the statement \u2014 state the values (temperature, humidity, duration, cycles). The engine checks stated numbers, never guesses." };
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
name: "conditions.check",
|
|
846
|
+
query: question,
|
|
847
|
+
output: JSON.stringify({ verdict: v.verdict, matched: v.matched, checks: v.checks })
|
|
848
|
+
};
|
|
849
|
+
} catch {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
};
|
|
417
854
|
var glossaryLookup = {
|
|
418
855
|
name: "glossary.lookup",
|
|
419
856
|
description: "Look up a defined term in the terminology datasets \u2014 the concept's definition and its defining publication. Bind everyday words to the defined term before interpreting a question.",
|
|
@@ -482,7 +919,7 @@ var certificatesSearch = {
|
|
|
482
919
|
return { name: "certificates.search", query, output };
|
|
483
920
|
}
|
|
484
921
|
};
|
|
485
|
-
var TOOLS_REGISTRY = [certificatesSearch, unitsGet, graphCites, docsSection, glossaryLookup, documentsFamily, licensedSection];
|
|
922
|
+
var TOOLS_REGISTRY = [certificatesSearch, unitsGet, graphCites, docsSection, glossaryLookup, documentsFamily, licensedSection, verdictEvaluate, conditionsCheck];
|
|
486
923
|
var TOOL_DECLARATION = [
|
|
487
924
|
"You may use one tool before answering, by writing a single line:",
|
|
488
925
|
...TOOLS_REGISTRY.filter((t) => t.audiences.includes("agent")).map((t) => {
|
|
@@ -513,6 +950,10 @@ export {
|
|
|
513
950
|
resolveBlocks,
|
|
514
951
|
contractV2,
|
|
515
952
|
tableRetyped,
|
|
953
|
+
evaluate,
|
|
954
|
+
verdictNote,
|
|
955
|
+
quantitiesIn,
|
|
956
|
+
evaluateConditionSets,
|
|
516
957
|
parseToolCall,
|
|
517
958
|
TOOLS_REGISTRY,
|
|
518
959
|
TOOL_DECLARATION,
|
package/dist/tools-content.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export declare const graphCites: ToolSpec;
|
|
|
4
4
|
export declare const docsSection: ToolSpec;
|
|
5
5
|
export declare const documentsFamily: ToolSpec;
|
|
6
6
|
export declare const licensedSection: ToolSpec;
|
|
7
|
+
export declare const verdictEvaluate: ToolSpec;
|
|
8
|
+
export declare const conditionsCheck: ToolSpec;
|
|
7
9
|
export declare const glossaryLookup: ToolSpec;
|
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
handleMemories,
|
|
5
5
|
scoreFaithfulness,
|
|
6
6
|
scoreJudge
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-EHABW23I.js";
|
|
8
8
|
import {
|
|
9
9
|
TOOLS_REGISTRY,
|
|
10
10
|
bindModelNode,
|
|
11
11
|
modelGroundingBlock
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-QBWZ6HVP.js";
|
|
13
13
|
import {
|
|
14
14
|
handleSearch
|
|
15
15
|
} from "../../chunk-TJTEBS4S.js";
|
|
@@ -973,12 +973,12 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
973
973
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
974
974
|
body: JSON.stringify({ ...args, stream: false })
|
|
975
975
|
});
|
|
976
|
-
const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-
|
|
976
|
+
const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-PI7IXAL4.js");
|
|
977
977
|
if (TOOLS_REGISTRY2.some((t) => t.name === name && t.audiences.includes("mcp"))) {
|
|
978
978
|
const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
|
|
979
979
|
return r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
|
|
980
980
|
}
|
|
981
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
981
|
+
const res = name === "ask" ? await (await import("../../ask-UG5UYRJV.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-G76DK4AF.js")).handleSearch(env, ctx, inner, tier, key);
|
|
982
982
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
983
983
|
});
|
|
984
984
|
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.92",
|
|
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",
|
|
@@ -11,7 +11,9 @@ import { portIndex, portModelRunner, hasLane } from "./env.ts";
|
|
|
11
11
|
import { embed } from "./ai.ts";
|
|
12
12
|
import { THRESHOLDS } from "./config.ts";
|
|
13
13
|
import { standardKeysFrom } from "./requestScope.ts";
|
|
14
|
-
import { licenseBoundaryRefusal, licensedEntryForDocNumber } from "./modelplane.ts";
|
|
14
|
+
import { licenseBoundaryRefusal, licensedEntryForDocNumber, licensedEntryForPackage } from "./modelplane.ts";
|
|
15
|
+
import { evaluate as evaluateVerdict } from "./verdict.ts";
|
|
16
|
+
import { evaluateConditionSets } from "./conditions.ts";
|
|
15
17
|
|
|
16
18
|
const slug = (docidentifier: string) =>
|
|
17
19
|
docidentifier
|
|
@@ -219,6 +221,124 @@ export const licensedSection: ToolSpec = {
|
|
|
219
221
|
},
|
|
220
222
|
};
|
|
221
223
|
|
|
224
|
+
|
|
225
|
+
// The deterministic engines as tools (the owner's 2026-10-04
|
|
226
|
+
// direction): computing with the standard, not reading it. Both wrap
|
|
227
|
+
// the engines the ask path already runs — no new evaluation logic
|
|
228
|
+
// lives here; the tool is the door, the engine is the authority.
|
|
229
|
+
// Void-when-parameters-miss is the honest contract: the tool states
|
|
230
|
+
// WHICH parameters the question did not state, never guesses.
|
|
231
|
+
|
|
232
|
+
export const verdictEvaluate: ToolSpec = {
|
|
233
|
+
name: "verdict.evaluate",
|
|
234
|
+
description:
|
|
235
|
+
"Deterministically evaluate a model-plane node's machine checks (OCL boolean expressions, threshold limits) against the quantities a question states. Returns pass/fail with every check's expression and values, or void with the missing parameter names when the question does not state enough. The machine computes; cite the node's clause.",
|
|
236
|
+
params: [
|
|
237
|
+
{ key: "node_id", required: true, description: "the model-plane node id, e.g. /req/metrological/repeatability (node ids appear in answers' verdict blocks)" },
|
|
238
|
+
{ key: "question", required: true, description: "the statement carrying the quantities, e.g. 'is mpe 0.02 with n_lc 3000 within the limit?'" },
|
|
239
|
+
],
|
|
240
|
+
audiences: ["mcp"],
|
|
241
|
+
handler: async (env, args) => {
|
|
242
|
+
const node_id = String(args?.node_id ?? "").trim();
|
|
243
|
+
const question = String(args?.question ?? "").trim();
|
|
244
|
+
if (!node_id.startsWith("/") || question.length < 4) return null;
|
|
245
|
+
try {
|
|
246
|
+
// the same zero-or-ambiguous rule bindModelNode applies: a node
|
|
247
|
+
// id that indexes under exactly one standard resolves; several
|
|
248
|
+
// or none resolve to nothing — no silent pick
|
|
249
|
+
const rows = await env.DB.prepare(
|
|
250
|
+
"SELECT standard, kind, name, content FROM model_nodes WHERE node_id = ?1 LIMIT 2",
|
|
251
|
+
)
|
|
252
|
+
.bind(node_id)
|
|
253
|
+
.all();
|
|
254
|
+
const found = (rows.results ?? []) as { standard: string; kind: string; name: string | null; content: string }[];
|
|
255
|
+
if (found.length !== 1) {
|
|
256
|
+
return {
|
|
257
|
+
name: "verdict.evaluate",
|
|
258
|
+
query: node_id,
|
|
259
|
+
output: found.length
|
|
260
|
+
? `The node id ${node_id} is indexed under several standards — name the standard to disambiguate. State this plainly.`
|
|
261
|
+
: `No model-plane node ${node_id} is indexed. State this plainly.`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const node = found[0]!;
|
|
265
|
+
const entry = licensedEntryForPackage(node.standard);
|
|
266
|
+
if (entry) {
|
|
267
|
+
return {
|
|
268
|
+
name: "verdict.evaluate",
|
|
269
|
+
query: node_id,
|
|
270
|
+
output: licenseBoundaryRefusal(entry.doc_number, null) ?? "License boundary — the node belongs to a licensed standard the caller's entitlement set does not cover.",
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
let content: unknown;
|
|
274
|
+
try { content = JSON.parse(node.content); } catch { content = null; }
|
|
275
|
+
const v = evaluateVerdict(content, question);
|
|
276
|
+
if (!v) {
|
|
277
|
+
return {
|
|
278
|
+
name: "verdict.evaluate",
|
|
279
|
+
query: node_id,
|
|
280
|
+
output: `The node ${node_id} (${node.kind}) carries no machine-checkable expressions — it is not evaluatable by the verdict engine. State this plainly.`,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
name: "verdict.evaluate",
|
|
285
|
+
query: node_id,
|
|
286
|
+
output: JSON.stringify({ standard: node.standard, node_id, kind: node.kind, name: node.name, verdict: v.verdict, on_violation: v.on_violation, missing: v.missing, checks: v.checks }),
|
|
287
|
+
};
|
|
288
|
+
} catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
export const conditionsCheck: ToolSpec = {
|
|
295
|
+
name: "conditions.check",
|
|
296
|
+
description:
|
|
297
|
+
"Check a stated combination of environmental quantities (temperature, humidity, duration, cycles) against the indexed severity/condition sets — the pass answer names the matched set and every band, the fail names the nearest set and its distance. Machine evaluation over the condition-set nodes; cite the matched set's clause.",
|
|
298
|
+
params: [
|
|
299
|
+
{ key: "question", required: true, description: "the stated combination, e.g. 'damp heat cyclic test at 55 °C for 2 cycles of 24 h'" },
|
|
300
|
+
{ key: "standard", required: false, description: "optional scope, the package id, e.g. iec-60068-2-30 (defaults to every indexed condition set the caller may see)" },
|
|
301
|
+
{ key: "licensed_standards", required: false, description: 'license entitlement keys for licensed condition sets, validated against the deployment\'s declared licenses' },
|
|
302
|
+
],
|
|
303
|
+
audiences: ["mcp"],
|
|
304
|
+
handler: async (env, args) => {
|
|
305
|
+
const question = String(args?.question ?? "").trim();
|
|
306
|
+
if (question.length < 4) return null;
|
|
307
|
+
const standard = args?.standard ? String(args.standard).trim() : null;
|
|
308
|
+
const keys = standardKeysFrom({ licensed_standards: args?.licensed_standards });
|
|
309
|
+
try {
|
|
310
|
+
const rows = standard
|
|
311
|
+
? await env.DB.prepare("SELECT node_id, standard, content FROM model_nodes WHERE kind = 'condition_set' AND standard = ?1").bind(standard).all()
|
|
312
|
+
: await env.DB.prepare("SELECT node_id, standard, content FROM model_nodes WHERE kind = 'condition_set'").all();
|
|
313
|
+
const all = ((rows.results ?? []) as { node_id: string; standard: string; content: string }[]).map((r) => {
|
|
314
|
+
let content: unknown = r.content;
|
|
315
|
+
try { content = JSON.parse(r.content); } catch { /* verbatim */ }
|
|
316
|
+
return { node_id: r.node_id, standard: r.standard, content };
|
|
317
|
+
});
|
|
318
|
+
// the ask path's own gate: licensed condition sets compete only
|
|
319
|
+
// for entitled callers — the same licensedEntryForPackage filter
|
|
320
|
+
const visible = all.filter((n) => {
|
|
321
|
+
const entry = licensedEntryForPackage(n.standard);
|
|
322
|
+
return !entry || keys.has(entry.key);
|
|
323
|
+
});
|
|
324
|
+
if (!visible.length) {
|
|
325
|
+
return { name: "conditions.check", query: question, output: "No condition sets are indexed (or visible to this caller's entitlements). State this plainly." };
|
|
326
|
+
}
|
|
327
|
+
const v = evaluateConditionSets(visible, question);
|
|
328
|
+
if (!v) {
|
|
329
|
+
return { name: "conditions.check", query: question, output: "No quantities were recognized in the statement — state the values (temperature, humidity, duration, cycles). The engine checks stated numbers, never guesses." };
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
name: "conditions.check",
|
|
333
|
+
query: question,
|
|
334
|
+
output: JSON.stringify({ verdict: v.verdict, matched: v.matched, checks: v.checks }),
|
|
335
|
+
};
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
222
342
|
export const glossaryLookup: ToolSpec = {
|
|
223
343
|
name: "glossary.lookup",
|
|
224
344
|
description:
|
|
@@ -64,9 +64,9 @@ const certificatesSearch: ToolSpec = {
|
|
|
64
64
|
},
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
import { unitsGet, graphCites, docsSection, glossaryLookup, documentsFamily, licensedSection } from "./tools-content.ts";
|
|
67
|
+
import { unitsGet, graphCites, docsSection, glossaryLookup, documentsFamily, licensedSection, verdictEvaluate, conditionsCheck } from "./tools-content.ts";
|
|
68
68
|
|
|
69
|
-
export const TOOLS_REGISTRY: ToolSpec[] = [certificatesSearch, unitsGet, graphCites, docsSection, glossaryLookup, documentsFamily, licensedSection];
|
|
69
|
+
export const TOOLS_REGISTRY: ToolSpec[] = [certificatesSearch, unitsGet, graphCites, docsSection, glossaryLookup, documentsFamily, licensedSection, verdictEvaluate, conditionsCheck];
|
|
70
70
|
|
|
71
71
|
/** The agent loop's protocol declaration, GENERATED from the registry —
|
|
72
72
|
* the model's tool vocabulary is always the registry's, never a
|