eyeling 1.24.0 → 1.24.2

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.
Files changed (165) hide show
  1. package/HANDBOOK.md +98 -0
  2. package/README.md +4 -4
  3. package/dist/browser/eyeling.browser.js +103 -0
  4. package/eyeling.js +103 -0
  5. package/lib/lexer.js +103 -0
  6. package/package.json +1 -1
  7. package/see/README.md +13 -11
  8. package/see/examples/_see.js +33 -2
  9. package/see/examples/age.js +37 -11
  10. package/see/examples/annotation.js +37 -11
  11. package/see/examples/backward.js +37 -11
  12. package/see/examples/backward_recursion.js +37 -11
  13. package/see/examples/bayes_diagnosis.js +37 -11
  14. package/see/examples/bayes_therapy.js +37 -11
  15. package/see/examples/bmi.js +37 -11
  16. package/see/examples/builtin_coverage.js +37 -11
  17. package/see/examples/collection.js +37 -11
  18. package/see/examples/complex.js +37 -11
  19. package/see/examples/complex_matrix_stability.js +37 -11
  20. package/see/examples/composition_of_injective_functions_is_injective.js +37 -11
  21. package/see/examples/control_system.js +37 -11
  22. package/see/examples/crypto_builtins_tests.js +37 -11
  23. package/see/examples/delfour.js +37 -11
  24. package/see/examples/digital_product_passport.js +37 -11
  25. package/see/examples/dijkstra.js +37 -11
  26. package/see/examples/dijkstra_risk_path.js +37 -11
  27. package/see/examples/doc/age.md +1 -1
  28. package/see/examples/doc/annotation.md +1 -1
  29. package/see/examples/doc/backward.md +1 -1
  30. package/see/examples/doc/backward_recursion.md +1 -1
  31. package/see/examples/doc/bayes_diagnosis.md +1 -1
  32. package/see/examples/doc/bayes_therapy.md +1 -1
  33. package/see/examples/doc/bmi.md +1 -1
  34. package/see/examples/doc/builtin_coverage.md +1 -1
  35. package/see/examples/doc/collection.md +1 -1
  36. package/see/examples/doc/complex.md +1 -1
  37. package/see/examples/doc/complex_matrix_stability.md +1 -1
  38. package/see/examples/doc/composition_of_injective_functions_is_injective.md +1 -1
  39. package/see/examples/doc/control_system.md +1 -1
  40. package/see/examples/doc/crypto_builtins_tests.md +1 -1
  41. package/see/examples/doc/delfour.md +1 -1
  42. package/see/examples/doc/digital_product_passport.md +1 -1
  43. package/see/examples/doc/dijkstra.md +1 -1
  44. package/see/examples/doc/dijkstra_risk_path.md +1 -1
  45. package/see/examples/doc/dog.md +1 -1
  46. package/see/examples/doc/eco_route_insight.md +1 -1
  47. package/see/examples/doc/equals.md +1 -1
  48. package/see/examples/doc/equivalence_classes_overlap_implies_same_class.md +1 -1
  49. package/see/examples/doc/euler_identity.md +1 -1
  50. package/see/examples/doc/ev_roundtrip_planner.md +1 -1
  51. package/see/examples/doc/existential_rule.md +1 -1
  52. package/see/examples/doc/expression_eval.md +1 -1
  53. package/see/examples/doc/family_cousins.md +1 -1
  54. package/see/examples/doc/fastpow.md +1 -1
  55. package/see/examples/doc/fibonacci.md +1 -1
  56. package/see/examples/doc/french_cities.md +1 -1
  57. package/see/examples/doc/fundamental_theorem_arithmetic.md +1 -1
  58. package/see/examples/doc/genetic_knapsack_selection.md +1 -1
  59. package/see/examples/doc/goldbach_1000.md +1 -1
  60. package/see/examples/doc/good_cobbler.md +1 -1
  61. package/see/examples/doc/gps.md +1 -1
  62. package/see/examples/doc/gray_code_counter.md +1 -1
  63. package/see/examples/doc/greatest_lower_bound_uniqueness.md +1 -1
  64. package/see/examples/doc/group_inverse_uniqueness.md +1 -1
  65. package/see/examples/doc/hadamard_approx.md +1 -1
  66. package/see/examples/doc/hanoi.md +1 -1
  67. package/see/examples/doc/odrl_dpv_risk_ranked.md +1 -1
  68. package/see/examples/doc/path_discovery.md +1 -1
  69. package/see/examples/doc/rc_discharge_envelope.md +1 -1
  70. package/see/examples/doc/rdf_message_flow.md +1 -1
  71. package/see/examples/doc/rdf_messages.md +1 -1
  72. package/see/examples/doc/school_placement_audit.md +1 -1
  73. package/see/examples/doc/smoke_arithmetic.md +1 -1
  74. package/see/examples/doc/socrates.md +1 -1
  75. package/see/examples/doc/triple_terms.md +26 -0
  76. package/see/examples/doc/wind_turbine.md +1 -1
  77. package/see/examples/doc/witch.md +1 -1
  78. package/see/examples/dog.js +37 -11
  79. package/see/examples/eco_route_insight.js +37 -11
  80. package/see/examples/equals.js +37 -11
  81. package/see/examples/equivalence_classes_overlap_implies_same_class.js +37 -11
  82. package/see/examples/euler_identity.js +37 -11
  83. package/see/examples/ev_roundtrip_planner.js +37 -11
  84. package/see/examples/existential_rule.js +37 -11
  85. package/see/examples/expression_eval.js +37 -11
  86. package/see/examples/family_cousins.js +37 -11
  87. package/see/examples/fastpow.js +37 -11
  88. package/see/examples/fibonacci.js +37 -11
  89. package/see/examples/french_cities.js +37 -11
  90. package/see/examples/fundamental_theorem_arithmetic.js +37 -11
  91. package/see/examples/genetic_knapsack_selection.js +37 -11
  92. package/see/examples/goldbach_1000.js +37 -11
  93. package/see/examples/good_cobbler.js +37 -11
  94. package/see/examples/gps.js +37 -11
  95. package/see/examples/gray_code_counter.js +37 -11
  96. package/see/examples/greatest_lower_bound_uniqueness.js +37 -11
  97. package/see/examples/group_inverse_uniqueness.js +37 -11
  98. package/see/examples/hadamard_approx.js +37 -11
  99. package/see/examples/hanoi.js +37 -11
  100. package/see/examples/input/triple_terms.trig +28 -0
  101. package/see/examples/n3/triple_terms.n3 +23 -0
  102. package/see/examples/odrl_dpv_risk_ranked.js +37 -11
  103. package/see/examples/output/age.md +3 -3
  104. package/see/examples/output/annotation.md +4 -4
  105. package/see/examples/output/backward.md +3 -3
  106. package/see/examples/output/backward_recursion.md +3 -3
  107. package/see/examples/output/bayes_diagnosis.md +1 -1
  108. package/see/examples/output/bayes_therapy.md +1 -1
  109. package/see/examples/output/bmi.md +1 -1
  110. package/see/examples/output/builtin_coverage.md +3 -3
  111. package/see/examples/output/collection.md +3 -3
  112. package/see/examples/output/complex.md +4 -4
  113. package/see/examples/output/complex_matrix_stability.md +1 -1
  114. package/see/examples/output/composition_of_injective_functions_is_injective.md +3 -3
  115. package/see/examples/output/control_system.md +3 -3
  116. package/see/examples/output/crypto_builtins_tests.md +3 -3
  117. package/see/examples/output/delfour.md +1 -1
  118. package/see/examples/output/digital_product_passport.md +1 -1
  119. package/see/examples/output/dijkstra.md +3 -3
  120. package/see/examples/output/dijkstra_risk_path.md +1 -1
  121. package/see/examples/output/dog.md +3 -3
  122. package/see/examples/output/eco_route_insight.md +1 -1
  123. package/see/examples/output/equals.md +3 -3
  124. package/see/examples/output/equivalence_classes_overlap_implies_same_class.md +3 -3
  125. package/see/examples/output/euler_identity.md +3 -3
  126. package/see/examples/output/ev_roundtrip_planner.md +1 -1
  127. package/see/examples/output/existential_rule.md +3 -3
  128. package/see/examples/output/expression_eval.md +3 -3
  129. package/see/examples/output/family_cousins.md +3 -3
  130. package/see/examples/output/fastpow.md +1 -1
  131. package/see/examples/output/fibonacci.md +1 -1
  132. package/see/examples/output/french_cities.md +3 -3
  133. package/see/examples/output/fundamental_theorem_arithmetic.md +1 -1
  134. package/see/examples/output/genetic_knapsack_selection.md +1 -1
  135. package/see/examples/output/goldbach_1000.md +1 -1
  136. package/see/examples/output/good_cobbler.md +4 -4
  137. package/see/examples/output/gps.md +1 -1
  138. package/see/examples/output/gray_code_counter.md +1 -1
  139. package/see/examples/output/greatest_lower_bound_uniqueness.md +3 -3
  140. package/see/examples/output/group_inverse_uniqueness.md +3 -3
  141. package/see/examples/output/hadamard_approx.md +3 -3
  142. package/see/examples/output/hanoi.md +3 -3
  143. package/see/examples/output/odrl_dpv_risk_ranked.md +3 -3
  144. package/see/examples/output/path_discovery.md +3 -3
  145. package/see/examples/output/rc_discharge_envelope.md +1 -1
  146. package/see/examples/output/rdf_message_flow.md +1 -1
  147. package/see/examples/output/rdf_messages.md +1 -1
  148. package/see/examples/output/school_placement_audit.md +1 -1
  149. package/see/examples/output/smoke_arithmetic.md +1 -1
  150. package/see/examples/output/socrates.md +3 -3
  151. package/see/examples/output/triple_terms.md +53 -0
  152. package/see/examples/output/wind_turbine.md +1 -1
  153. package/see/examples/output/witch.md +3 -3
  154. package/see/examples/path_discovery.js +37 -11
  155. package/see/examples/rc_discharge_envelope.js +37 -11
  156. package/see/examples/rdf_message_flow.js +37 -11
  157. package/see/examples/rdf_messages.js +37 -11
  158. package/see/examples/school_placement_audit.js +37 -11
  159. package/see/examples/smoke_arithmetic.js +37 -11
  160. package/see/examples/socrates.js +37 -11
  161. package/see/examples/triple_terms.js +1442 -0
  162. package/see/examples/wind_turbine.js +37 -11
  163. package/see/examples/witch.js +37 -11
  164. package/see/see.js +455 -94
  165. package/test/api.test.js +20 -0
@@ -8,6 +8,7 @@ const crypto = require('crypto');
8
8
 
9
9
  function canonical(term) {
10
10
  if (term.kind === 'list') return ['list', term.items.map(canonical)];
11
+ if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
11
12
  if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
12
13
  return [term.kind, term.value];
13
14
  }
@@ -17,6 +18,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
17
18
  function termIsConcrete(t) {
18
19
  if (!t || t.kind === 'var') return false;
19
20
  if (t.kind === 'list') return t.items.every(termIsConcrete);
21
+ if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
20
22
  if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
21
23
  return true;
22
24
  }
@@ -32,6 +34,7 @@ function primitive(t) {
32
34
  if (t.kind === 'iri') return t.value.replace(/^:/, '');
33
35
  if (t.kind === 'blank') return t.value;
34
36
  if (t.kind === 'list') return t.items.map(primitive);
37
+ if (t.kind === 'triple') return termToN3(t);
35
38
  if (t.kind === 'formula') return termToN3(t);
36
39
  return undefined;
37
40
  }
@@ -52,6 +55,7 @@ function termToN3(t) {
52
55
  if (t.kind === 'var') return '?' + t.value;
53
56
  if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
54
57
  if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
58
+ if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
55
59
  if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
56
60
  return String(t.value ?? t);
57
61
  }
@@ -74,6 +78,7 @@ function resolve(term, env, seen = new Set()) {
74
78
  return resolve(env[term.value], env, seen);
75
79
  }
76
80
  if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
81
+ if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
77
82
  if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
78
83
  return term;
79
84
  }
@@ -91,6 +96,14 @@ function unify(a, b, env) {
91
96
  }
92
97
  return out;
93
98
  }
99
+ if (a.kind === 'triple' || b.kind === 'triple') {
100
+ if (a.kind !== 'triple' || b.kind !== 'triple') return null;
101
+ let out = unify(a.s, b.s, env);
102
+ if (!out) return null;
103
+ out = unify(a.p, b.p, out);
104
+ if (!out) return null;
105
+ return unify(a.o, b.o, out);
106
+ }
94
107
  return deepEqual(a, b) ? env : null;
95
108
  }
96
109
  function bind(pattern, value, env) { return unify(pattern, value, env); }
@@ -106,6 +119,7 @@ function termIsGround(t, env) {
106
119
  const r = resolve(t, env);
107
120
  if (r.kind === 'var') return false;
108
121
  if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
122
+ if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
109
123
  if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
110
124
  return true;
111
125
  }
@@ -665,6 +679,7 @@ function instantiate(term, env, ruleId) {
665
679
  }
666
680
  if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
667
681
  if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
682
+ if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
668
683
  if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
669
684
  return cloneTerm(term);
670
685
  }
@@ -1040,19 +1055,19 @@ function renderStructuredOutput({ title, graph, queries = [], rules = [], initia
1040
1055
  const lines = [];
1041
1056
  lines.push('# ' + title);
1042
1057
  lines.push('');
1043
- lines.push('## Insight');
1058
+ lines.push('## Entailment');
1044
1059
  if (mode === 'query') {
1045
1060
  lines.push('The compiled query selected ' + selected.length + ' fact(s) after the rule closure was computed.');
1046
1061
  } else if (mode === 'formula') {
1047
- lines.push('The derivation produced ' + selected.length + ' formula-valued conclusion(s).');
1062
+ lines.push('The derivation produced ' + selected.length + ' formula-valued entailment(s).');
1048
1063
  } else {
1049
1064
  lines.push('The derivation produced ' + derived.length + ' new fact(s) from ' + initialFacts.length + ' stated fact(s).');
1050
1065
  }
1051
- if (keyFact) lines.push('Main conclusion: **' + factSentence(keyFact) + '**');
1066
+ if (keyFact) lines.push('Main entailment: **' + factSentence(keyFact) + '**');
1052
1067
  const bullets = selected.slice(-6).reverse();
1053
1068
  if (bullets.length) {
1054
1069
  lines.push('');
1055
- lines.push('Selected conclusions:');
1070
+ lines.push('Selected entailments:');
1056
1071
  for (const fact of bullets) lines.push('- ' + codeFact(fact));
1057
1072
  }
1058
1073
  lines.push('');
@@ -1114,15 +1129,15 @@ function dedupeExplanationHeadings(text) {
1114
1129
  function normalizePublicReport(markdown, title) {
1115
1130
  let text = String(markdown || '').trimEnd();
1116
1131
  if (!/^\s*#\s+/m.test(text)) text = '# ' + title + '\n\n' + text;
1117
- if (!/^##\s+Insight\s*$/mi.test(text)) {
1118
- text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Insight\n');
1132
+ if (!/^##\s+Entailment\s*$/mi.test(text)) {
1133
+ text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Entailment\n');
1119
1134
  }
1120
1135
  if (!/^##\s+Explanation\s*$/mi.test(text)) {
1121
1136
  text += '\n\n## Explanation\nNo additional explanation was provided by the generated output.';
1122
1137
  }
1123
1138
  text = text.replace(/^##\s+([^\n]+?)\s*$/gm, (line, heading) => {
1124
1139
  const normalized = heading.trim().toLowerCase();
1125
- if (normalized === 'insight' || normalized === 'explanation') return '## ' + (normalized === 'insight' ? 'Insight' : 'Explanation');
1140
+ if (normalized === 'insight' || normalized === 'conclusion' || normalized === 'entailment' || normalized === 'explanation') return '## ' + (normalized === 'explanation' ? 'Explanation' : 'Entailment');
1126
1141
  return '**' + heading.trim() + '**';
1127
1142
  });
1128
1143
  text = dedupeExplanationHeadings(text);
@@ -1131,13 +1146,13 @@ function normalizePublicReport(markdown, title) {
1131
1146
  function markdownize(raw, title) {
1132
1147
  let text = String(raw || '');
1133
1148
  text = text
1134
- .replace(/===\s*Answer\s*===/g, '## Insight')
1149
+ .replace(/===\s*Answer\s*===/g, '## Entailment')
1135
1150
  .replace(/===\s*Reason\s+Why\s*===/gi, '## Explanation')
1136
1151
  .replace(/===\s*Explanation\s*===/gi, '## Explanation')
1137
1152
  .replace(/===\s*([^=]+?)\s*===/g, (_, h) => '**' + h.trim() + '**');
1138
1153
  text = text.replace(/^C(\d+)\s+OK\s*-\s*/gm, 'C$1: ');
1139
1154
  text = dedupeExplanationHeadings(text);
1140
- if (!text.trim()) text = '## Insight\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
1155
+ if (!text.trim()) text = '## Entailment\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
1141
1156
  return normalizePublicReport(text, title);
1142
1157
  }
1143
1158
  function authoredSupportAppendix(graph, queries, rules, initialFacts, trace) {
@@ -2731,7 +2746,7 @@ const QUERIES = [
2731
2746
  ]
2732
2747
  }
2733
2748
  ];
2734
- const DOC_MARKDOWN = "# Digital Product Passport\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the smartphone Digital Product Passport example. The\nrules fold component mass, recycled content, critical raw materials, public\ndocuments, lifecycle footprint, and access-policy validations into a public PASS.\n\n## Compilation summary\n\n- Example name: `digital_product_passport`\n- Input facts emitted: 90\n- Forward rules compiled: 12\n- Backward predicate rules compiled: 0\n- Fuses compiled: 2\n- Predicate count: 53\n\n## Built-ins used\n\n- `log:notEqualTo`\n- `log:outputString`\n- `math:greaterThan`\n- `math:integerQuotient`\n- `math:notGreaterThan`\n- `math:product`\n- `math:sum`\n- `string:format`\n- `string:notGreaterThan`\n\n## Runtime model\n\nThe generated `examples/digital_product_passport.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/digital_product_passport.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/digital-product-passport.trig` or `examples/input/digital_product_passport.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/digital_product_passport.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
2749
+ const DOC_MARKDOWN = "# Digital Product Passport\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the smartphone Digital Product Passport example. The\nrules fold component mass, recycled content, critical raw materials, public\ndocuments, lifecycle footprint, and access-policy validations into a public PASS.\n\n## Compilation summary\n\n- Example name: `digital_product_passport`\n- Input facts emitted: 90\n- Forward rules compiled: 12\n- Backward predicate rules compiled: 0\n- Fuses compiled: 2\n- Predicate count: 53\n\n## Built-ins used\n\n- `log:notEqualTo`\n- `log:outputString`\n- `math:greaterThan`\n- `math:integerQuotient`\n- `math:notGreaterThan`\n- `math:product`\n- `math:sum`\n- `string:format`\n- `string:notGreaterThan`\n\n## Runtime model\n\nThe generated `examples/digital_product_passport.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/digital_product_passport.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/digital-product-passport.trig` or `examples/input/digital_product_passport.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/digital_product_passport.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
2735
2750
  function seeMetadata(data) { return (data && data.__see) || {}; }
2736
2751
  function trustedDerivation(data) { const meta = seeMetadata(data); const facts = data && Array.isArray(data.facts) ? data.facts : []; const expectedFacts = EXPECTED_INPUT_FACTS || Number(meta.InputFacts || 0); if (meta.SourceSHA256 && meta.SourceSHA256 !== "502e99d8e51a930eba50cb2411263cb6db5d98d63fe3dd48254612d993a9ab95") throw new Error('input evidence does not match the N3 source compiled into this example'); const result = saturate(facts, RULES); const rawOutput = renderRawOutput(result.graph, QUERIES, RULES, facts); fail('Compiled N3 derivation failed', { 'input evidence metadata is present and matches compiled source': meta.SourceSHA256 === "502e99d8e51a930eba50cb2411263cb6db5d98d63fe3dd48254612d993a9ab95", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 14, 'compiled query directives were loaded': QUERIES.length === 1, 'a derivation fixpoint was reached': result.graph.facts.length >= facts.length, 'query or output facts were produced': rawOutput.length > 0 }); return { ...result, rawOutput, inputFacts: facts }; }
2737
2752
  function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
@@ -2765,6 +2780,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
2765
2780
  }
2766
2781
  return out;
2767
2782
  }
2783
+ function termHasTripleTerm(term) {
2784
+ if (!term) return false;
2785
+ if (term.kind === 'triple') return true;
2786
+ if (term.kind === 'list') return term.items.some(termHasTripleTerm);
2787
+ if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
2788
+ return false;
2789
+ }
2790
+ function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
2791
+ function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
2792
+ function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
2768
2793
  function trigGraphBlock(label, atoms) {
2769
2794
  const lines = [label + ' {'];
2770
2795
  for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
@@ -2812,7 +2837,8 @@ function formalOutputToTrig(facts, trig) {
2812
2837
  const prefixes = prefixLinesFromTrig(trig);
2813
2838
  if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
2814
2839
  const nl = String.fromCharCode(10);
2815
- return prefixes.join(nl) + nl + nl + body.join(nl);
2840
+ const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
2841
+ return version + prefixes.join(nl) + nl + nl + body.join(nl);
2816
2842
  }
2817
2843
  function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
2818
2844
  const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
@@ -8,6 +8,7 @@ const crypto = require('crypto');
8
8
 
9
9
  function canonical(term) {
10
10
  if (term.kind === 'list') return ['list', term.items.map(canonical)];
11
+ if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
11
12
  if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
12
13
  return [term.kind, term.value];
13
14
  }
@@ -17,6 +18,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
17
18
  function termIsConcrete(t) {
18
19
  if (!t || t.kind === 'var') return false;
19
20
  if (t.kind === 'list') return t.items.every(termIsConcrete);
21
+ if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
20
22
  if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
21
23
  return true;
22
24
  }
@@ -32,6 +34,7 @@ function primitive(t) {
32
34
  if (t.kind === 'iri') return t.value.replace(/^:/, '');
33
35
  if (t.kind === 'blank') return t.value;
34
36
  if (t.kind === 'list') return t.items.map(primitive);
37
+ if (t.kind === 'triple') return termToN3(t);
35
38
  if (t.kind === 'formula') return termToN3(t);
36
39
  return undefined;
37
40
  }
@@ -52,6 +55,7 @@ function termToN3(t) {
52
55
  if (t.kind === 'var') return '?' + t.value;
53
56
  if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
54
57
  if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
58
+ if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
55
59
  if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
56
60
  return String(t.value ?? t);
57
61
  }
@@ -74,6 +78,7 @@ function resolve(term, env, seen = new Set()) {
74
78
  return resolve(env[term.value], env, seen);
75
79
  }
76
80
  if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
81
+ if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
77
82
  if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
78
83
  return term;
79
84
  }
@@ -91,6 +96,14 @@ function unify(a, b, env) {
91
96
  }
92
97
  return out;
93
98
  }
99
+ if (a.kind === 'triple' || b.kind === 'triple') {
100
+ if (a.kind !== 'triple' || b.kind !== 'triple') return null;
101
+ let out = unify(a.s, b.s, env);
102
+ if (!out) return null;
103
+ out = unify(a.p, b.p, out);
104
+ if (!out) return null;
105
+ return unify(a.o, b.o, out);
106
+ }
94
107
  return deepEqual(a, b) ? env : null;
95
108
  }
96
109
  function bind(pattern, value, env) { return unify(pattern, value, env); }
@@ -106,6 +119,7 @@ function termIsGround(t, env) {
106
119
  const r = resolve(t, env);
107
120
  if (r.kind === 'var') return false;
108
121
  if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
122
+ if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
109
123
  if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
110
124
  return true;
111
125
  }
@@ -665,6 +679,7 @@ function instantiate(term, env, ruleId) {
665
679
  }
666
680
  if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
667
681
  if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
682
+ if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
668
683
  if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
669
684
  return cloneTerm(term);
670
685
  }
@@ -1040,19 +1055,19 @@ function renderStructuredOutput({ title, graph, queries = [], rules = [], initia
1040
1055
  const lines = [];
1041
1056
  lines.push('# ' + title);
1042
1057
  lines.push('');
1043
- lines.push('## Insight');
1058
+ lines.push('## Entailment');
1044
1059
  if (mode === 'query') {
1045
1060
  lines.push('The compiled query selected ' + selected.length + ' fact(s) after the rule closure was computed.');
1046
1061
  } else if (mode === 'formula') {
1047
- lines.push('The derivation produced ' + selected.length + ' formula-valued conclusion(s).');
1062
+ lines.push('The derivation produced ' + selected.length + ' formula-valued entailment(s).');
1048
1063
  } else {
1049
1064
  lines.push('The derivation produced ' + derived.length + ' new fact(s) from ' + initialFacts.length + ' stated fact(s).');
1050
1065
  }
1051
- if (keyFact) lines.push('Main conclusion: **' + factSentence(keyFact) + '**');
1066
+ if (keyFact) lines.push('Main entailment: **' + factSentence(keyFact) + '**');
1052
1067
  const bullets = selected.slice(-6).reverse();
1053
1068
  if (bullets.length) {
1054
1069
  lines.push('');
1055
- lines.push('Selected conclusions:');
1070
+ lines.push('Selected entailments:');
1056
1071
  for (const fact of bullets) lines.push('- ' + codeFact(fact));
1057
1072
  }
1058
1073
  lines.push('');
@@ -1114,15 +1129,15 @@ function dedupeExplanationHeadings(text) {
1114
1129
  function normalizePublicReport(markdown, title) {
1115
1130
  let text = String(markdown || '').trimEnd();
1116
1131
  if (!/^\s*#\s+/m.test(text)) text = '# ' + title + '\n\n' + text;
1117
- if (!/^##\s+Insight\s*$/mi.test(text)) {
1118
- text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Insight\n');
1132
+ if (!/^##\s+Entailment\s*$/mi.test(text)) {
1133
+ text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Entailment\n');
1119
1134
  }
1120
1135
  if (!/^##\s+Explanation\s*$/mi.test(text)) {
1121
1136
  text += '\n\n## Explanation\nNo additional explanation was provided by the generated output.';
1122
1137
  }
1123
1138
  text = text.replace(/^##\s+([^\n]+?)\s*$/gm, (line, heading) => {
1124
1139
  const normalized = heading.trim().toLowerCase();
1125
- if (normalized === 'insight' || normalized === 'explanation') return '## ' + (normalized === 'insight' ? 'Insight' : 'Explanation');
1140
+ if (normalized === 'insight' || normalized === 'conclusion' || normalized === 'entailment' || normalized === 'explanation') return '## ' + (normalized === 'explanation' ? 'Explanation' : 'Entailment');
1126
1141
  return '**' + heading.trim() + '**';
1127
1142
  });
1128
1143
  text = dedupeExplanationHeadings(text);
@@ -1131,13 +1146,13 @@ function normalizePublicReport(markdown, title) {
1131
1146
  function markdownize(raw, title) {
1132
1147
  let text = String(raw || '');
1133
1148
  text = text
1134
- .replace(/===\s*Answer\s*===/g, '## Insight')
1149
+ .replace(/===\s*Answer\s*===/g, '## Entailment')
1135
1150
  .replace(/===\s*Reason\s+Why\s*===/gi, '## Explanation')
1136
1151
  .replace(/===\s*Explanation\s*===/gi, '## Explanation')
1137
1152
  .replace(/===\s*([^=]+?)\s*===/g, (_, h) => '**' + h.trim() + '**');
1138
1153
  text = text.replace(/^C(\d+)\s+OK\s*-\s*/gm, 'C$1: ');
1139
1154
  text = dedupeExplanationHeadings(text);
1140
- if (!text.trim()) text = '## Insight\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
1155
+ if (!text.trim()) text = '## Entailment\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
1141
1156
  return normalizePublicReport(text, title);
1142
1157
  }
1143
1158
  function authoredSupportAppendix(graph, queries, rules, initialFacts, trace) {
@@ -1945,7 +1960,7 @@ const RULES = [
1945
1960
  }
1946
1961
  ];
1947
1962
  const QUERIES = [];
1948
- const DOC_MARKDOWN = "# Dijkstra's algorithm to find the shortest path\n\nGenerated by `see.js` from a Notation3 source file.\n\n## Compilation summary\n\n- Example name: `dijkstra`\n- Input facts emitted: 9\n- Forward rules compiled: 2\n- Backward predicate rules compiled: 3\n- Fuses compiled: 0\n- Predicate count: 9\n\n## Built-ins used\n\n- `list:append`\n- `list:firstRest`\n- `list:reverse`\n- `list:sort`\n- `log:collectAllIn`\n\n## Runtime model\n\nThe generated `examples/dijkstra.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/dijkstra.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/dijkstra.trig` or `examples/input/dijkstra.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/dijkstra.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
1963
+ const DOC_MARKDOWN = "# Dijkstra's algorithm to find the shortest path\n\nGenerated by `see.js` from a Notation3 source file.\n\n## Compilation summary\n\n- Example name: `dijkstra`\n- Input facts emitted: 9\n- Forward rules compiled: 2\n- Backward predicate rules compiled: 3\n- Fuses compiled: 0\n- Predicate count: 9\n\n## Built-ins used\n\n- `list:append`\n- `list:firstRest`\n- `list:reverse`\n- `list:sort`\n- `log:collectAllIn`\n\n## Runtime model\n\nThe generated `examples/dijkstra.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/dijkstra.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/dijkstra.trig` or `examples/input/dijkstra.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/dijkstra.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
1949
1964
  function seeMetadata(data) { return (data && data.__see) || {}; }
1950
1965
  function trustedDerivation(data) { const meta = seeMetadata(data); const facts = data && Array.isArray(data.facts) ? data.facts : []; const expectedFacts = EXPECTED_INPUT_FACTS || Number(meta.InputFacts || 0); if (meta.SourceSHA256 && meta.SourceSHA256 !== "e877c1cfea99078228bd424f6e55761e56cc54bc1e64b21540231620cc3620ab") throw new Error('input evidence does not match the N3 source compiled into this example'); const result = saturate(facts, RULES); const rawOutput = renderRawOutput(result.graph, QUERIES, RULES, facts); fail('Compiled N3 derivation failed', { 'input evidence metadata is present and matches compiled source': meta.SourceSHA256 === "e877c1cfea99078228bd424f6e55761e56cc54bc1e64b21540231620cc3620ab", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 5, 'compiled query directives were loaded': QUERIES.length === 0, 'a derivation fixpoint was reached': result.graph.facts.length >= facts.length, 'query or output facts were produced': rawOutput.length > 0 }); return { ...result, rawOutput, inputFacts: facts }; }
1951
1966
  function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
@@ -1979,6 +1994,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
1979
1994
  }
1980
1995
  return out;
1981
1996
  }
1997
+ function termHasTripleTerm(term) {
1998
+ if (!term) return false;
1999
+ if (term.kind === 'triple') return true;
2000
+ if (term.kind === 'list') return term.items.some(termHasTripleTerm);
2001
+ if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
2002
+ return false;
2003
+ }
2004
+ function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
2005
+ function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
2006
+ function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
1982
2007
  function trigGraphBlock(label, atoms) {
1983
2008
  const lines = [label + ' {'];
1984
2009
  for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
@@ -2026,7 +2051,8 @@ function formalOutputToTrig(facts, trig) {
2026
2051
  const prefixes = prefixLinesFromTrig(trig);
2027
2052
  if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
2028
2053
  const nl = String.fromCharCode(10);
2029
- return prefixes.join(nl) + nl + nl + body.join(nl);
2054
+ const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
2055
+ return version + prefixes.join(nl) + nl + nl + body.join(nl);
2030
2056
  }
2031
2057
  function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
2032
2058
  const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
@@ -8,6 +8,7 @@ const crypto = require('crypto');
8
8
 
9
9
  function canonical(term) {
10
10
  if (term.kind === 'list') return ['list', term.items.map(canonical)];
11
+ if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
11
12
  if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
12
13
  return [term.kind, term.value];
13
14
  }
@@ -17,6 +18,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
17
18
  function termIsConcrete(t) {
18
19
  if (!t || t.kind === 'var') return false;
19
20
  if (t.kind === 'list') return t.items.every(termIsConcrete);
21
+ if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
20
22
  if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
21
23
  return true;
22
24
  }
@@ -32,6 +34,7 @@ function primitive(t) {
32
34
  if (t.kind === 'iri') return t.value.replace(/^:/, '');
33
35
  if (t.kind === 'blank') return t.value;
34
36
  if (t.kind === 'list') return t.items.map(primitive);
37
+ if (t.kind === 'triple') return termToN3(t);
35
38
  if (t.kind === 'formula') return termToN3(t);
36
39
  return undefined;
37
40
  }
@@ -52,6 +55,7 @@ function termToN3(t) {
52
55
  if (t.kind === 'var') return '?' + t.value;
53
56
  if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
54
57
  if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
58
+ if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
55
59
  if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
56
60
  return String(t.value ?? t);
57
61
  }
@@ -74,6 +78,7 @@ function resolve(term, env, seen = new Set()) {
74
78
  return resolve(env[term.value], env, seen);
75
79
  }
76
80
  if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
81
+ if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
77
82
  if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
78
83
  return term;
79
84
  }
@@ -91,6 +96,14 @@ function unify(a, b, env) {
91
96
  }
92
97
  return out;
93
98
  }
99
+ if (a.kind === 'triple' || b.kind === 'triple') {
100
+ if (a.kind !== 'triple' || b.kind !== 'triple') return null;
101
+ let out = unify(a.s, b.s, env);
102
+ if (!out) return null;
103
+ out = unify(a.p, b.p, out);
104
+ if (!out) return null;
105
+ return unify(a.o, b.o, out);
106
+ }
94
107
  return deepEqual(a, b) ? env : null;
95
108
  }
96
109
  function bind(pattern, value, env) { return unify(pattern, value, env); }
@@ -106,6 +119,7 @@ function termIsGround(t, env) {
106
119
  const r = resolve(t, env);
107
120
  if (r.kind === 'var') return false;
108
121
  if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
122
+ if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
109
123
  if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
110
124
  return true;
111
125
  }
@@ -665,6 +679,7 @@ function instantiate(term, env, ruleId) {
665
679
  }
666
680
  if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
667
681
  if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
682
+ if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
668
683
  if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
669
684
  return cloneTerm(term);
670
685
  }
@@ -1040,19 +1055,19 @@ function renderStructuredOutput({ title, graph, queries = [], rules = [], initia
1040
1055
  const lines = [];
1041
1056
  lines.push('# ' + title);
1042
1057
  lines.push('');
1043
- lines.push('## Insight');
1058
+ lines.push('## Entailment');
1044
1059
  if (mode === 'query') {
1045
1060
  lines.push('The compiled query selected ' + selected.length + ' fact(s) after the rule closure was computed.');
1046
1061
  } else if (mode === 'formula') {
1047
- lines.push('The derivation produced ' + selected.length + ' formula-valued conclusion(s).');
1062
+ lines.push('The derivation produced ' + selected.length + ' formula-valued entailment(s).');
1048
1063
  } else {
1049
1064
  lines.push('The derivation produced ' + derived.length + ' new fact(s) from ' + initialFacts.length + ' stated fact(s).');
1050
1065
  }
1051
- if (keyFact) lines.push('Main conclusion: **' + factSentence(keyFact) + '**');
1066
+ if (keyFact) lines.push('Main entailment: **' + factSentence(keyFact) + '**');
1052
1067
  const bullets = selected.slice(-6).reverse();
1053
1068
  if (bullets.length) {
1054
1069
  lines.push('');
1055
- lines.push('Selected conclusions:');
1070
+ lines.push('Selected entailments:');
1056
1071
  for (const fact of bullets) lines.push('- ' + codeFact(fact));
1057
1072
  }
1058
1073
  lines.push('');
@@ -1114,15 +1129,15 @@ function dedupeExplanationHeadings(text) {
1114
1129
  function normalizePublicReport(markdown, title) {
1115
1130
  let text = String(markdown || '').trimEnd();
1116
1131
  if (!/^\s*#\s+/m.test(text)) text = '# ' + title + '\n\n' + text;
1117
- if (!/^##\s+Insight\s*$/mi.test(text)) {
1118
- text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Insight\n');
1132
+ if (!/^##\s+Entailment\s*$/mi.test(text)) {
1133
+ text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Entailment\n');
1119
1134
  }
1120
1135
  if (!/^##\s+Explanation\s*$/mi.test(text)) {
1121
1136
  text += '\n\n## Explanation\nNo additional explanation was provided by the generated output.';
1122
1137
  }
1123
1138
  text = text.replace(/^##\s+([^\n]+?)\s*$/gm, (line, heading) => {
1124
1139
  const normalized = heading.trim().toLowerCase();
1125
- if (normalized === 'insight' || normalized === 'explanation') return '## ' + (normalized === 'insight' ? 'Insight' : 'Explanation');
1140
+ if (normalized === 'insight' || normalized === 'conclusion' || normalized === 'entailment' || normalized === 'explanation') return '## ' + (normalized === 'explanation' ? 'Explanation' : 'Entailment');
1126
1141
  return '**' + heading.trim() + '**';
1127
1142
  });
1128
1143
  text = dedupeExplanationHeadings(text);
@@ -1131,13 +1146,13 @@ function normalizePublicReport(markdown, title) {
1131
1146
  function markdownize(raw, title) {
1132
1147
  let text = String(raw || '');
1133
1148
  text = text
1134
- .replace(/===\s*Answer\s*===/g, '## Insight')
1149
+ .replace(/===\s*Answer\s*===/g, '## Entailment')
1135
1150
  .replace(/===\s*Reason\s+Why\s*===/gi, '## Explanation')
1136
1151
  .replace(/===\s*Explanation\s*===/gi, '## Explanation')
1137
1152
  .replace(/===\s*([^=]+?)\s*===/g, (_, h) => '**' + h.trim() + '**');
1138
1153
  text = text.replace(/^C(\d+)\s+OK\s*-\s*/gm, 'C$1: ');
1139
1154
  text = dedupeExplanationHeadings(text);
1140
- if (!text.trim()) text = '## Insight\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
1155
+ if (!text.trim()) text = '## Entailment\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
1141
1156
  return normalizePublicReport(text, title);
1142
1157
  }
1143
1158
  function authoredSupportAppendix(graph, queries, rules, initialFacts, trace) {
@@ -1749,7 +1764,7 @@ const QUERIES = [
1749
1764
  ]
1750
1765
  }
1751
1766
  ];
1752
- const DOC_MARKDOWN = "# Dijkstra Risk Path\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the risk-adjusted route example. The original JSON\ninput is preserved as the data-input sidecar; this source compiles the\nexecutable derivation and report.\n\n## Compilation summary\n\n- Example name: `dijkstra_risk_path`\n- Input facts emitted: 21\n- Forward rules compiled: 3\n- Backward predicate rules compiled: 0\n- Fuses compiled: 0\n- Predicate count: 17\n\n## Built-ins used\n\n- `log:outputString`\n- `math:lessThan`\n- `string:format`\n\n## Runtime model\n\nThe generated `examples/dijkstra_risk_path.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/dijkstra_risk_path.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/dijkstra-risk-path.trig` or `examples/input/dijkstra_risk_path.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/dijkstra_risk_path.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
1767
+ const DOC_MARKDOWN = "# Dijkstra Risk Path\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the risk-adjusted route example. The original JSON\ninput is preserved as the data-input sidecar; this source compiles the\nexecutable derivation and report.\n\n## Compilation summary\n\n- Example name: `dijkstra_risk_path`\n- Input facts emitted: 21\n- Forward rules compiled: 3\n- Backward predicate rules compiled: 0\n- Fuses compiled: 0\n- Predicate count: 17\n\n## Built-ins used\n\n- `log:outputString`\n- `math:lessThan`\n- `string:format`\n\n## Runtime model\n\nThe generated `examples/dijkstra_risk_path.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/dijkstra_risk_path.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/dijkstra-risk-path.trig` or `examples/input/dijkstra_risk_path.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/dijkstra_risk_path.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
1753
1768
  function seeMetadata(data) { return (data && data.__see) || {}; }
1754
1769
  function trustedDerivation(data) { const meta = seeMetadata(data); const facts = data && Array.isArray(data.facts) ? data.facts : []; const expectedFacts = EXPECTED_INPUT_FACTS || Number(meta.InputFacts || 0); if (meta.SourceSHA256 && meta.SourceSHA256 !== "a11f23a58252e557817c5d26cabdfee5ce8c5c08a40a54ab680eaa8960c88937") throw new Error('input evidence does not match the N3 source compiled into this example'); const result = saturate(facts, RULES); const rawOutput = renderRawOutput(result.graph, QUERIES, RULES, facts); fail('Compiled N3 derivation failed', { 'input evidence metadata is present and matches compiled source': meta.SourceSHA256 === "a11f23a58252e557817c5d26cabdfee5ce8c5c08a40a54ab680eaa8960c88937", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 3, 'compiled query directives were loaded': QUERIES.length === 1, 'a derivation fixpoint was reached': result.graph.facts.length >= facts.length, 'query or output facts were produced': rawOutput.length > 0 }); return { ...result, rawOutput, inputFacts: facts }; }
1755
1770
  function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
@@ -1783,6 +1798,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
1783
1798
  }
1784
1799
  return out;
1785
1800
  }
1801
+ function termHasTripleTerm(term) {
1802
+ if (!term) return false;
1803
+ if (term.kind === 'triple') return true;
1804
+ if (term.kind === 'list') return term.items.some(termHasTripleTerm);
1805
+ if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
1806
+ return false;
1807
+ }
1808
+ function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
1809
+ function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
1810
+ function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
1786
1811
  function trigGraphBlock(label, atoms) {
1787
1812
  const lines = [label + ' {'];
1788
1813
  for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
@@ -1830,7 +1855,8 @@ function formalOutputToTrig(facts, trig) {
1830
1855
  const prefixes = prefixLinesFromTrig(trig);
1831
1856
  if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
1832
1857
  const nl = String.fromCharCode(10);
1833
- return prefixes.join(nl) + nl + nl + body.join(nl);
1858
+ const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
1859
+ return version + prefixes.join(nl) + nl + nl + body.join(nl);
1834
1860
  }
1835
1861
  function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
1836
1862
  const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
@@ -24,4 +24,4 @@ The generated `examples/age.js` is a specialized JavaScript derivation program.
24
24
 
25
25
  ## Output model
26
26
 
27
- Running `node examples/age.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
27
+ Running `node examples/age.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -21,4 +21,4 @@ The generated `examples/annotation.js` is a specialized JavaScript derivation pr
21
21
 
22
22
  ## Output model
23
23
 
24
- Running `node examples/annotation.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
24
+ Running `node examples/annotation.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -23,4 +23,4 @@ The generated `examples/backward.js` is a specialized JavaScript derivation prog
23
23
 
24
24
  ## Output model
25
25
 
26
- Running `node examples/backward.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
26
+ Running `node examples/backward.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -23,4 +23,4 @@ The generated `examples/backward_recursion.js` is a specialized JavaScript deriv
23
23
 
24
24
  ## Output model
25
25
 
26
- Running `node examples/backward_recursion.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
26
+ Running `node examples/backward_recursion.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -38,4 +38,4 @@ The generated `examples/bayes_diagnosis.js` is a specialized JavaScript derivati
38
38
 
39
39
  ## Output model
40
40
 
41
- Running `node examples/bayes_diagnosis.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
41
+ Running `node examples/bayes_diagnosis.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -37,4 +37,4 @@ The generated `examples/bayes_therapy.js` is a specialized JavaScript derivation
37
37
 
38
38
  ## Output model
39
39
 
40
- Running `node examples/bayes_therapy.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
40
+ Running `node examples/bayes_therapy.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -35,4 +35,4 @@ The generated `examples/bmi.js` is a specialized JavaScript derivation program.
35
35
 
36
36
  ## Output model
37
37
 
38
- Running `node examples/bmi.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
38
+ Running `node examples/bmi.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -50,4 +50,4 @@ The generated `examples/builtin_coverage.js` is a specialized JavaScript derivat
50
50
 
51
51
  ## Output model
52
52
 
53
- Running `node examples/builtin_coverage.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
53
+ Running `node examples/builtin_coverage.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -21,4 +21,4 @@ The generated `examples/collection.js` is a specialized JavaScript derivation pr
21
21
 
22
22
  ## Output model
23
23
 
24
- Running `node examples/collection.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
24
+ Running `node examples/collection.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -35,4 +35,4 @@ The generated `examples/complex.js` is a specialized JavaScript derivation progr
35
35
 
36
36
  ## Output model
37
37
 
38
- Running `node examples/complex.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
38
+ Running `node examples/complex.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -32,4 +32,4 @@ The generated `examples/complex_matrix_stability.js` is a specialized JavaScript
32
32
 
33
33
  ## Output model
34
34
 
35
- Running `node examples/complex_matrix_stability.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
35
+ Running `node examples/complex_matrix_stability.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -21,4 +21,4 @@ The generated `examples/composition_of_injective_functions_is_injective.js` is a
21
21
 
22
22
  ## Output model
23
23
 
24
- Running `node examples/composition_of_injective_functions_is_injective.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
24
+ Running `node examples/composition_of_injective_functions_is_injective.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -29,4 +29,4 @@ The generated `examples/control_system.js` is a specialized JavaScript derivatio
29
29
 
30
30
  ## Output model
31
31
 
32
- Running `node examples/control_system.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
32
+ Running `node examples/control_system.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -24,4 +24,4 @@ The generated `examples/crypto_builtins_tests.js` is a specialized JavaScript de
24
24
 
25
25
  ## Output model
26
26
 
27
- Running `node examples/crypto_builtins_tests.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
27
+ Running `node examples/crypto_builtins_tests.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
@@ -34,4 +34,4 @@ The generated `examples/delfour.js` is a specialized JavaScript derivation progr
34
34
 
35
35
  ## Output model
36
36
 
37
- Running `node examples/delfour.js` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.
37
+ Running `node examples/delfour.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.