@skillstech/thunderlang 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,46 @@
3
3
  All notable changes to `@skillstech/thunderlang`. Pre-1.0: the language and the
4
4
  `intent-graph-v1` schema version independently and may still change.
5
5
 
6
+ ## 0.2.0
7
+
8
+ The cross-language target execution release. Additive; no breaking changes.
9
+
10
+ Version alignment: starting with 0.2.0, ThunderLang and OpenThunder version in lockstep, since most
11
+ OpenThunder changes land in the language first. (0.1.8 and 0.1.9 were never published; the last
12
+ release on npm was 0.1.7.)
13
+
14
+ - **Live target adapters.** `thunder test <file> --target typescript|python|csharp|java` and
15
+ `thunder conform <file> --run <targets>` now compile the generated decision(s) and execute them
16
+ for real, grading the actual outputs instead of fed results. TypeScript/JS runs in-process,
17
+ Python through `python3`, C# through a throwaway `dotnet` console project, and Java through the
18
+ JDK 11+ single-file launcher (`java ThunderTarget.java`).
19
+ - **`--all-targets`.** `thunder test <file> --all-targets` and `thunder conform <file> --all-targets`
20
+ run every target whose toolchain is available in one pass, side by side, and skip any target whose
21
+ runtime is absent (it stays declared in the conformance matrix) rather than failing.
22
+ - **Static-target type inference.** Because C# and Java are statically typed, each decision
23
+ parameter's type is inferred from the test-case values it receives (numeric wins, then boolean,
24
+ else string), and each language gets its own equality translation.
25
+ - Missing toolchains are detected via cached smoke checks and skipped cleanly, so builds never fail
26
+ for a runtime that is not installed.
27
+
28
+ ## 0.1.8
29
+
30
+ The testing and verification release. Additive; no breaking changes.
31
+
32
+ - **Rename to ThunderLang.** Package is `@skillstech/thunderlang`; the CLI binary is `thunder`
33
+ (with `intent` kept as a legacy alias). `.thunder` is the canonical source extension; `.tl` and
34
+ `.intent` are accepted. The Intent Graph and Intent-Oriented Programming vocabulary are retained.
35
+ - **`thunder prove`** emits an `intent-proof-v1` artifact with honest per-claim verdicts
36
+ (verified / failed / declared / needs_verification, with `provenBy`). An unverified claim never
37
+ reads as proven.
38
+ - **`thunder test --contracts [--strict]`** derives an obligation from every `guarantee` and
39
+ `never`, resolved against the specific test that verifies it. `--strict` fails CI on any unverified.
40
+ - **Verification classification**: `verify by assertion|static|runtime|evidence|tool|approval|formal`.
41
+ - **Stable IDs**: explicit `id INV-G-001` on guarantees / never-rules (slug fallback).
42
+ - **Proof freshness**: the proof records intent hash, compiler, git commit, dependency-lockfile hash,
43
+ and environment; `thunder verify` marks a proof STALE when the implementation, dependencies, or
44
+ compiler move since it was generated.
45
+
6
46
  ## 0.1.6
7
47
 
8
48
  The agent-conformance release (skills on intents + 12-factor scoring). Additive; no breaking
package/dist/core.cjs CHANGED
@@ -789,6 +789,60 @@ function parseDecision(name, node2) {
789
789
  }
790
790
  return dec;
791
791
  }
792
+ function parseProperty(name, node2) {
793
+ const prop = { name, vars: [], decide: null, expects: [], line: node2.line };
794
+ for (const c of node2.children.filter((x) => !isNote(x))) {
795
+ const k = firstWord(c.text);
796
+ const a = rest(c.text);
797
+ if (k === "forAll" || k === "forall") {
798
+ for (const v of c.children.filter((x) => !isNote(x))) {
799
+ const m = v.text.trim().match(/^([A-Za-z_]\w*)\s*:\s*(\w+)(?:\s+where\s+(.+))?$/);
800
+ if (!m) continue;
801
+ const whereChild = (v.children || []).find((x) => firstWord(x.text) === "where");
802
+ prop.vars.push({ name: m[1], type: m[2], where: m[3] || (whereChild ? rest(whereChild.text) : null) });
803
+ }
804
+ } else if (k === "decide") prop.decide = a || c.children[0] && c.children[0].text.trim() || null;
805
+ else if (k === "when") {
806
+ const dm = a.match(/(?:decide|execute)\s+(\w+)/);
807
+ if (dm) prop.decide = dm[1];
808
+ } else if (k === "expect") for (const e of c.children.filter((x) => !isNote(x))) prop.expects.push(e.text.trim());
809
+ }
810
+ return prop;
811
+ }
812
+ function parseEvaluation(name, node2) {
813
+ const ev = { name, dataset: null, requires: [], line: node2.line };
814
+ for (const c of node2.children.filter((x) => !isNote(x))) {
815
+ const k = firstWord(c.text);
816
+ const a = rest(c.text);
817
+ if (k === "dataset") ev.dataset = a || c.children[0] && c.children[0].text.trim() || null;
818
+ else if (k === "require") {
819
+ for (const r of c.children.filter((x) => !isNote(x))) {
820
+ const m = r.text.trim().match(/^([A-Za-z_][\w.]*)\s*(>=|<=|==|!=|>|<)\s*(-?[\d.]+)$/);
821
+ if (m) ev.requires.push({ metric: m[1], op: m[2], threshold: parseFloat(m[3]) });
822
+ }
823
+ }
824
+ }
825
+ return ev;
826
+ }
827
+ function parseScenario(name, node2) {
828
+ const sc = { name, given: [], when: [], then: [], never: [], eventually: [], line: node2.line };
829
+ const clausesOf = (c) => {
830
+ const items = leafItems(c);
831
+ return items.length ? items : rest(c.text) ? [rest(c.text)] : [];
832
+ };
833
+ for (const c of node2.children.filter((x) => !isNote(x))) {
834
+ const k = firstWord(c.text);
835
+ if (k === "given") sc.given.push(...clausesOf(c));
836
+ else if (k === "when") sc.when.push(...clausesOf(c));
837
+ else if (k === "then") sc.then.push(...clausesOf(c));
838
+ else if (k === "never") sc.never.push(...clausesOf(c));
839
+ else if (k === "eventually") {
840
+ const within = (rest(c.text).match(/within\s+(.+)/) || [])[1] || null;
841
+ sc.eventually.push({ within, clauses: leafItems(c) });
842
+ }
843
+ }
844
+ return sc;
845
+ }
792
846
  function parseLifecycle(name, node2) {
793
847
  const lc = { name, states: [], transitions: [], terminals: [], line: node2.line };
794
848
  for (const c of node2.children.filter((x) => !isNote(x))) {
@@ -821,7 +875,13 @@ function parseInvariant(name, node2) {
821
875
  for (const c of kids(node2)) {
822
876
  const k = firstWord(c.text);
823
877
  if (k === "verify") inv.verify.push(rest(c.text));
824
- else if (k === "because") inv.because = rest(c.text);
878
+ else if (k === "id") {
879
+ const v = rest(c.text);
880
+ if (v) {
881
+ inv.id = v;
882
+ inv.stableId = v;
883
+ }
884
+ } else if (k === "because") inv.because = rest(c.text);
825
885
  else if (k === "statement") inv.statement = rest(c.text) || leafItems(c).join(" ");
826
886
  else if (k === "scope") inv.scope = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
827
887
  else if (k === "severity") inv.severity = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
@@ -907,11 +967,12 @@ function parseHandler(trigger, node2) {
907
967
  actions
908
968
  };
909
969
  }
970
+ var VERIFY_KINDS = /* @__PURE__ */ new Set(["assertion", "static", "runtime", "evidence", "tool", "approval", "formal"]);
910
971
  function upsertRule(list, statement, line) {
911
972
  const id = slug(statement);
912
973
  let r = list.find((x) => x.id === id);
913
974
  if (!r) {
914
- r = { id, statement, because: null, verify: [], notes: [], line };
975
+ r = { id, statement, because: null, verify: [], verifications: [], notes: [], line };
915
976
  list.push(r);
916
977
  }
917
978
  return r;
@@ -920,8 +981,28 @@ function applyDetail(rule, node2) {
920
981
  for (const c of node2.children) {
921
982
  const kw = firstWord(c.text);
922
983
  if (kw === "because") rule.because = rest(c.text) || c.children[0] && c.children[0].text || null;
923
- else if (kw === "verify") rule.verify.push(rest(c.text) || c.children[0] && c.children[0].text || "");
924
- else if (isNote(c)) rule.notes.push(parseNoteNode(c));
984
+ else if (kw === "verify") {
985
+ const a = rest(c.text);
986
+ const m = a && a.match(/^by\s+(\w+)/i);
987
+ if (m) {
988
+ const kind = m[1].toLowerCase();
989
+ const details = (c.children || []).filter((x) => !isNote(x)).map((x) => x.text.trim()).filter(Boolean);
990
+ rule.verifications.push({ kind: VERIFY_KINDS.has(kind) ? kind : "unknown", declared: kind, details });
991
+ rule.verify.push(details.length ? `${kind}: ${details.join("; ")}` : `by ${kind}`);
992
+ } else {
993
+ const v = a || c.children[0] && c.children[0].text || "";
994
+ if (v) {
995
+ rule.verify.push(v);
996
+ rule.verifications.push({ kind: "unclassified", declared: null, details: [v] });
997
+ }
998
+ }
999
+ } else if (kw === "id") {
1000
+ const v = rest(c.text) || c.children[0] && c.children[0].text;
1001
+ if (v) {
1002
+ rule.id = v;
1003
+ rule.stableId = v;
1004
+ }
1005
+ } else if (isNote(c)) rule.notes.push(parseNoteNode(c));
925
1006
  }
926
1007
  rule.verify = rule.verify.filter(Boolean);
927
1008
  }
@@ -986,6 +1067,9 @@ function parseIntent(source) {
986
1067
  handlers: [],
987
1068
  // Decisions, rules, process (Gap 4)
988
1069
  decisions: [],
1070
+ properties: [],
1071
+ scenarios: [],
1072
+ evaluations: [],
989
1073
  // Governance: waivers , governed exceptions to blocking diagnostics (Gap 5)
990
1074
  waivers: [],
991
1075
  // Data purpose + privacy , governed data elements (Gap 6)
@@ -1214,6 +1298,15 @@ function parseIntent(source) {
1214
1298
  case "decision":
1215
1299
  ast.decisions.push(parseDecision(arg, node2));
1216
1300
  break;
1301
+ case "property":
1302
+ ast.properties.push(parseProperty(arg, node2));
1303
+ break;
1304
+ case "scenario":
1305
+ ast.scenarios.push(parseScenario(arg, node2));
1306
+ break;
1307
+ case "evaluation":
1308
+ ast.evaluations.push(parseEvaluation(arg, node2));
1309
+ break;
1217
1310
  // ── System profile ──
1218
1311
  case "capability": {
1219
1312
  const cap = { name: arg, description: null, implements: [], line: node2.line };
@@ -3198,21 +3291,23 @@ function compileExpr(src) {
3198
3291
  function evalExpr(src, inputs = {}) {
3199
3292
  return compileExpr(src)(inputs);
3200
3293
  }
3294
+ var C_LOGIC = { and: (a, b) => `(${a} && ${b})`, or: (a, b) => `(${a} || ${b})`, not: (a) => `!${a}`, bool: (v) => String(v) };
3201
3295
  var DIALECTS = {
3202
- js: { eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
3203
- csharp: { eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
3204
- java: { eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" }
3296
+ js: { ...C_LOGIC, eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
3297
+ csharp: { ...C_LOGIC, eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
3298
+ java: { ...C_LOGIC, eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" },
3299
+ python: { and: (a, b) => `(${a} and ${b})`, or: (a, b) => `(${a} or ${b})`, not: (a) => `(not ${a})`, bool: (v) => v ? "True" : "False", eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `${x} in [${list.join(", ")}]`, nil: "None" }
3205
3300
  };
3206
3301
  function renderExpr(src, inputs, D) {
3207
3302
  const known = new Set(inputs);
3208
3303
  const r = (n) => {
3209
3304
  switch (n.k) {
3210
3305
  case "or":
3211
- return `(${r(n.a)} || ${r(n.b)})`;
3306
+ return D.or(r(n.a), r(n.b));
3212
3307
  case "and":
3213
- return `(${r(n.a)} && ${r(n.b)})`;
3308
+ return D.and(r(n.a), r(n.b));
3214
3309
  case "not":
3215
- return `!${r(n.a)}`;
3310
+ return D.not(r(n.a));
3216
3311
  case "neg":
3217
3312
  return `-${r(n.a)}`;
3218
3313
  case "arith":
@@ -3226,7 +3321,7 @@ function renderExpr(src, inputs, D) {
3226
3321
  case "list":
3227
3322
  return `[${n.items.map(r).join(", ")}]`;
3228
3323
  case "lit":
3229
- return typeof n.v === "string" ? JSON.stringify(n.v) : String(n.v);
3324
+ return typeof n.v === "string" ? JSON.stringify(n.v) : typeof n.v === "boolean" ? D.bool(n.v) : String(n.v);
3230
3325
  case "ref":
3231
3326
  return known.has(n.path) || known.has(n.path.split(".")[0]) ? n.path : JSON.stringify(n.path);
3232
3327
  default:
@@ -4193,7 +4288,7 @@ function notesSummary(ast) {
4193
4288
  byLens
4194
4289
  };
4195
4290
  }
4196
- var COMPILER_VERSION = "0.1.0";
4291
+ var COMPILER_VERSION = "0.2.0";
4197
4292
  var PROOF_SCHEMA_VERSION = "0.1.0";
4198
4293
  var SOURCE_PRODUCT = "skillstech-compiler";
4199
4294
  function buildContractGraph(ast, generatedAt) {
@@ -4686,13 +4781,15 @@ function buildProof(ast, { sourceFile, sourceHash, targetsRequested, targetsGene
4686
4781
  id: g.id,
4687
4782
  text: g.statement,
4688
4783
  status: g.verify.length > 0 || verifiedText ? "planned" : "needs_verification",
4689
- evidence: g.verify
4784
+ evidence: g.verify,
4785
+ verifications: g.verifications || []
4690
4786
  })),
4691
4787
  neverRules: ast.neverRules.map((n) => ({
4692
4788
  id: n.id,
4693
4789
  text: n.statement,
4694
4790
  status: n.verify.length > 0 ? "planned" : "needs_verification",
4695
- evidence: n.verify
4791
+ evidence: n.verify,
4792
+ verifications: n.verifications || []
4696
4793
  })),
4697
4794
  errors: (ast.errors || []).map((e) => ({ name: e.name })),
4698
4795
  examples: (ast.examples || []).map((ex) => ({ given: ex.given, expect: ex.expect })),
package/dist/index.cjs CHANGED
@@ -450,6 +450,60 @@ function parseDecision(name, node2) {
450
450
  }
451
451
  return dec;
452
452
  }
453
+ function parseProperty(name, node2) {
454
+ const prop = { name, vars: [], decide: null, expects: [], line: node2.line };
455
+ for (const c of node2.children.filter((x) => !isNote(x))) {
456
+ const k = firstWord(c.text);
457
+ const a = rest(c.text);
458
+ if (k === "forAll" || k === "forall") {
459
+ for (const v of c.children.filter((x) => !isNote(x))) {
460
+ const m = v.text.trim().match(/^([A-Za-z_]\w*)\s*:\s*(\w+)(?:\s+where\s+(.+))?$/);
461
+ if (!m) continue;
462
+ const whereChild = (v.children || []).find((x) => firstWord(x.text) === "where");
463
+ prop.vars.push({ name: m[1], type: m[2], where: m[3] || (whereChild ? rest(whereChild.text) : null) });
464
+ }
465
+ } else if (k === "decide") prop.decide = a || c.children[0] && c.children[0].text.trim() || null;
466
+ else if (k === "when") {
467
+ const dm = a.match(/(?:decide|execute)\s+(\w+)/);
468
+ if (dm) prop.decide = dm[1];
469
+ } else if (k === "expect") for (const e of c.children.filter((x) => !isNote(x))) prop.expects.push(e.text.trim());
470
+ }
471
+ return prop;
472
+ }
473
+ function parseEvaluation(name, node2) {
474
+ const ev = { name, dataset: null, requires: [], line: node2.line };
475
+ for (const c of node2.children.filter((x) => !isNote(x))) {
476
+ const k = firstWord(c.text);
477
+ const a = rest(c.text);
478
+ if (k === "dataset") ev.dataset = a || c.children[0] && c.children[0].text.trim() || null;
479
+ else if (k === "require") {
480
+ for (const r of c.children.filter((x) => !isNote(x))) {
481
+ const m = r.text.trim().match(/^([A-Za-z_][\w.]*)\s*(>=|<=|==|!=|>|<)\s*(-?[\d.]+)$/);
482
+ if (m) ev.requires.push({ metric: m[1], op: m[2], threshold: parseFloat(m[3]) });
483
+ }
484
+ }
485
+ }
486
+ return ev;
487
+ }
488
+ function parseScenario(name, node2) {
489
+ const sc = { name, given: [], when: [], then: [], never: [], eventually: [], line: node2.line };
490
+ const clausesOf = (c) => {
491
+ const items = leafItems(c);
492
+ return items.length ? items : rest(c.text) ? [rest(c.text)] : [];
493
+ };
494
+ for (const c of node2.children.filter((x) => !isNote(x))) {
495
+ const k = firstWord(c.text);
496
+ if (k === "given") sc.given.push(...clausesOf(c));
497
+ else if (k === "when") sc.when.push(...clausesOf(c));
498
+ else if (k === "then") sc.then.push(...clausesOf(c));
499
+ else if (k === "never") sc.never.push(...clausesOf(c));
500
+ else if (k === "eventually") {
501
+ const within = (rest(c.text).match(/within\s+(.+)/) || [])[1] || null;
502
+ sc.eventually.push({ within, clauses: leafItems(c) });
503
+ }
504
+ }
505
+ return sc;
506
+ }
453
507
  function parseLifecycle(name, node2) {
454
508
  const lc = { name, states: [], transitions: [], terminals: [], line: node2.line };
455
509
  for (const c of node2.children.filter((x) => !isNote(x))) {
@@ -482,7 +536,13 @@ function parseInvariant(name, node2) {
482
536
  for (const c of kids(node2)) {
483
537
  const k = firstWord(c.text);
484
538
  if (k === "verify") inv.verify.push(rest(c.text));
485
- else if (k === "because") inv.because = rest(c.text);
539
+ else if (k === "id") {
540
+ const v = rest(c.text);
541
+ if (v) {
542
+ inv.id = v;
543
+ inv.stableId = v;
544
+ }
545
+ } else if (k === "because") inv.because = rest(c.text);
486
546
  else if (k === "statement") inv.statement = rest(c.text) || leafItems(c).join(" ");
487
547
  else if (k === "scope") inv.scope = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
488
548
  else if (k === "severity") inv.severity = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
@@ -568,11 +628,12 @@ function parseHandler(trigger, node2) {
568
628
  actions
569
629
  };
570
630
  }
631
+ var VERIFY_KINDS = /* @__PURE__ */ new Set(["assertion", "static", "runtime", "evidence", "tool", "approval", "formal"]);
571
632
  function upsertRule(list, statement, line) {
572
633
  const id = slug(statement);
573
634
  let r = list.find((x) => x.id === id);
574
635
  if (!r) {
575
- r = { id, statement, because: null, verify: [], notes: [], line };
636
+ r = { id, statement, because: null, verify: [], verifications: [], notes: [], line };
576
637
  list.push(r);
577
638
  }
578
639
  return r;
@@ -581,8 +642,28 @@ function applyDetail(rule, node2) {
581
642
  for (const c of node2.children) {
582
643
  const kw = firstWord(c.text);
583
644
  if (kw === "because") rule.because = rest(c.text) || c.children[0] && c.children[0].text || null;
584
- else if (kw === "verify") rule.verify.push(rest(c.text) || c.children[0] && c.children[0].text || "");
585
- else if (isNote(c)) rule.notes.push(parseNoteNode(c));
645
+ else if (kw === "verify") {
646
+ const a = rest(c.text);
647
+ const m = a && a.match(/^by\s+(\w+)/i);
648
+ if (m) {
649
+ const kind = m[1].toLowerCase();
650
+ const details = (c.children || []).filter((x) => !isNote(x)).map((x) => x.text.trim()).filter(Boolean);
651
+ rule.verifications.push({ kind: VERIFY_KINDS.has(kind) ? kind : "unknown", declared: kind, details });
652
+ rule.verify.push(details.length ? `${kind}: ${details.join("; ")}` : `by ${kind}`);
653
+ } else {
654
+ const v = a || c.children[0] && c.children[0].text || "";
655
+ if (v) {
656
+ rule.verify.push(v);
657
+ rule.verifications.push({ kind: "unclassified", declared: null, details: [v] });
658
+ }
659
+ }
660
+ } else if (kw === "id") {
661
+ const v = rest(c.text) || c.children[0] && c.children[0].text;
662
+ if (v) {
663
+ rule.id = v;
664
+ rule.stableId = v;
665
+ }
666
+ } else if (isNote(c)) rule.notes.push(parseNoteNode(c));
586
667
  }
587
668
  rule.verify = rule.verify.filter(Boolean);
588
669
  }
@@ -647,6 +728,9 @@ function parseIntent(source) {
647
728
  handlers: [],
648
729
  // Decisions, rules, process (Gap 4)
649
730
  decisions: [],
731
+ properties: [],
732
+ scenarios: [],
733
+ evaluations: [],
650
734
  // Governance: waivers , governed exceptions to blocking diagnostics (Gap 5)
651
735
  waivers: [],
652
736
  // Data purpose + privacy , governed data elements (Gap 6)
@@ -875,6 +959,15 @@ function parseIntent(source) {
875
959
  case "decision":
876
960
  ast.decisions.push(parseDecision(arg, node2));
877
961
  break;
962
+ case "property":
963
+ ast.properties.push(parseProperty(arg, node2));
964
+ break;
965
+ case "scenario":
966
+ ast.scenarios.push(parseScenario(arg, node2));
967
+ break;
968
+ case "evaluation":
969
+ ast.evaluations.push(parseEvaluation(arg, node2));
970
+ break;
878
971
  // ── System profile ──
879
972
  case "capability": {
880
973
  const cap = { name: arg, description: null, implements: [], line: node2.line };
@@ -2194,7 +2287,7 @@ function notesSummary(ast) {
2194
2287
  byLens
2195
2288
  };
2196
2289
  }
2197
- var COMPILER_VERSION = "0.1.0";
2290
+ var COMPILER_VERSION = "0.2.0";
2198
2291
  var PROOF_SCHEMA_VERSION = "0.1.0";
2199
2292
  var SOURCE_PRODUCT = "skillstech-compiler";
2200
2293
  function buildContractGraph(ast, generatedAt) {
@@ -2687,13 +2780,15 @@ function buildProof(ast, { sourceFile, sourceHash, targetsRequested, targetsGene
2687
2780
  id: g.id,
2688
2781
  text: g.statement,
2689
2782
  status: g.verify.length > 0 || verifiedText ? "planned" : "needs_verification",
2690
- evidence: g.verify
2783
+ evidence: g.verify,
2784
+ verifications: g.verifications || []
2691
2785
  })),
2692
2786
  neverRules: ast.neverRules.map((n) => ({
2693
2787
  id: n.id,
2694
2788
  text: n.statement,
2695
2789
  status: n.verify.length > 0 ? "planned" : "needs_verification",
2696
- evidence: n.verify
2790
+ evidence: n.verify,
2791
+ verifications: n.verifications || []
2697
2792
  })),
2698
2793
  errors: (ast.errors || []).map((e) => ({ name: e.name })),
2699
2794
  examples: (ast.examples || []).map((ex) => ({ given: ex.given, expect: ex.expect })),
@@ -6088,21 +6183,23 @@ function compileExpr(src) {
6088
6183
  function evalExpr(src, inputs = {}) {
6089
6184
  return compileExpr(src)(inputs);
6090
6185
  }
6186
+ var C_LOGIC = { and: (a, b) => `(${a} && ${b})`, or: (a, b) => `(${a} || ${b})`, not: (a) => `!${a}`, bool: (v) => String(v) };
6091
6187
  var DIALECTS = {
6092
- js: { eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
6093
- csharp: { eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
6094
- java: { eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" }
6188
+ js: { ...C_LOGIC, eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
6189
+ csharp: { ...C_LOGIC, eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
6190
+ java: { ...C_LOGIC, eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" },
6191
+ python: { and: (a, b) => `(${a} and ${b})`, or: (a, b) => `(${a} or ${b})`, not: (a) => `(not ${a})`, bool: (v) => v ? "True" : "False", eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `${x} in [${list.join(", ")}]`, nil: "None" }
6095
6192
  };
6096
6193
  function renderExpr(src, inputs, D) {
6097
6194
  const known = new Set(inputs);
6098
6195
  const r = (n) => {
6099
6196
  switch (n.k) {
6100
6197
  case "or":
6101
- return `(${r(n.a)} || ${r(n.b)})`;
6198
+ return D.or(r(n.a), r(n.b));
6102
6199
  case "and":
6103
- return `(${r(n.a)} && ${r(n.b)})`;
6200
+ return D.and(r(n.a), r(n.b));
6104
6201
  case "not":
6105
- return `!${r(n.a)}`;
6202
+ return D.not(r(n.a));
6106
6203
  case "neg":
6107
6204
  return `-${r(n.a)}`;
6108
6205
  case "arith":
@@ -6116,7 +6213,7 @@ function renderExpr(src, inputs, D) {
6116
6213
  case "list":
6117
6214
  return `[${n.items.map(r).join(", ")}]`;
6118
6215
  case "lit":
6119
- return typeof n.v === "string" ? JSON.stringify(n.v) : String(n.v);
6216
+ return typeof n.v === "string" ? JSON.stringify(n.v) : typeof n.v === "boolean" ? D.bool(n.v) : String(n.v);
6120
6217
  case "ref":
6121
6218
  return known.has(n.path) || known.has(n.path.split(".")[0]) ? n.path : JSON.stringify(n.path);
6122
6219
  default:
@@ -6237,7 +6334,7 @@ function runTests(ast) {
6237
6334
  const inputs = coerce(c.given);
6238
6335
  const run = evaluateDecision(dec, inputs);
6239
6336
  const pass = c.expect == null || String(run.result) === String(c.expect);
6240
- results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
6337
+ results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, matched: run.matched, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
6241
6338
  } else if (lc) {
6242
6339
  const sim = simulateLifecycle(lc, c.events || []);
6243
6340
  const passState = c.expect == null || sim.finalState === c.expect;