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) {
@@ -1469,7 +1484,7 @@ const QUERIES = [
1469
1484
  ]
1470
1485
  }
1471
1486
  ];
1472
- const DOC_MARKDOWN = "# Fibonacci Example (Big)\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the exact Fibonacci example. The target and sample indices are represented as formal TriG input\nevidence when this source is compiled.\n\n## Compilation summary\n\n- Example name: `fibonacci`\n- Input facts emitted: 27\n- Forward rules compiled: 1\n- Backward predicate rules compiled: 0\n- Fuses compiled: 0\n- Predicate count: 11\n\n## Built-ins used\n\n- `log:outputString`\n- `string:format`\n\n## Runtime model\n\nThe generated `examples/fibonacci.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/fibonacci.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/fibonacci.trig` or `examples/input/fibonacci.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/fibonacci.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";
1487
+ const DOC_MARKDOWN = "# Fibonacci Example (Big)\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the exact Fibonacci example. The target and sample indices are represented as formal TriG input\nevidence when this source is compiled.\n\n## Compilation summary\n\n- Example name: `fibonacci`\n- Input facts emitted: 27\n- Forward rules compiled: 1\n- Backward predicate rules compiled: 0\n- Fuses compiled: 0\n- Predicate count: 11\n\n## Built-ins used\n\n- `log:outputString`\n- `string:format`\n\n## Runtime model\n\nThe generated `examples/fibonacci.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/fibonacci.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/fibonacci.trig` or `examples/input/fibonacci.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/fibonacci.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";
1473
1488
  function seeMetadata(data) { return (data && data.__see) || {}; }
1474
1489
  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 !== "25d93f4de352e797e1afb277f9f5805b1cdaface366f199b1195cc601555a172") 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 === "25d93f4de352e797e1afb277f9f5805b1cdaface366f199b1195cc601555a172", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 1, '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 }; }
1475
1490
  function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
@@ -1503,6 +1518,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
1503
1518
  }
1504
1519
  return out;
1505
1520
  }
1521
+ function termHasTripleTerm(term) {
1522
+ if (!term) return false;
1523
+ if (term.kind === 'triple') return true;
1524
+ if (term.kind === 'list') return term.items.some(termHasTripleTerm);
1525
+ if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
1526
+ return false;
1527
+ }
1528
+ function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
1529
+ function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
1530
+ function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
1506
1531
  function trigGraphBlock(label, atoms) {
1507
1532
  const lines = [label + ' {'];
1508
1533
  for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
@@ -1550,7 +1575,8 @@ function formalOutputToTrig(facts, trig) {
1550
1575
  const prefixes = prefixLinesFromTrig(trig);
1551
1576
  if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
1552
1577
  const nl = String.fromCharCode(10);
1553
- return prefixes.join(nl) + nl + nl + body.join(nl);
1578
+ const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
1579
+ return version + prefixes.join(nl) + nl + nl + body.join(nl);
1554
1580
  }
1555
1581
  function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
1556
1582
  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) {
@@ -1367,7 +1382,7 @@ const QUERIES = [
1367
1382
  ]
1368
1383
  }
1369
1384
  ];
1370
- const DOC_MARKDOWN = "# French cities — graph path traversal.\n\nGenerated by `see.js` from a Notation3 source file.\n\nBased on the EYE reasoning/graph example: graph.axiom.n3 plus graph.filter.n3.\nIt asks whether Paris can reach Nantes by following one-way links, using a\nsubproperty rule and a transitive-property rule.\n\n## Compilation summary\n\n- Example name: `french_cities`\n- Input facts emitted: 12\n- Forward rules compiled: 2\n- Backward predicate rules compiled: 0\n- Fuses compiled: 0\n- Predicate count: 6\n\n## Built-ins used\n\n- none\n\n## Runtime model\n\nThe generated `examples/french_cities.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/french_cities.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/french-cities.trig` or `examples/input/french_cities.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/french_cities.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";
1385
+ const DOC_MARKDOWN = "# French cities — graph path traversal.\n\nGenerated by `see.js` from a Notation3 source file.\n\nBased on the EYE reasoning/graph example: graph.axiom.n3 plus graph.filter.n3.\nIt asks whether Paris can reach Nantes by following one-way links, using a\nsubproperty rule and a transitive-property rule.\n\n## Compilation summary\n\n- Example name: `french_cities`\n- Input facts emitted: 12\n- Forward rules compiled: 2\n- Backward predicate rules compiled: 0\n- Fuses compiled: 0\n- Predicate count: 6\n\n## Built-ins used\n\n- none\n\n## Runtime model\n\nThe generated `examples/french_cities.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/french_cities.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/french-cities.trig` or `examples/input/french_cities.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/french_cities.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";
1371
1386
  function seeMetadata(data) { return (data && data.__see) || {}; }
1372
1387
  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 !== "7e3672e6ab314845d44cdd16e7a91484c205de77ff8c9a06971f857612692be4") 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 === "7e3672e6ab314845d44cdd16e7a91484c205de77ff8c9a06971f857612692be4", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 2, '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 }; }
1373
1388
  function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
@@ -1401,6 +1416,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
1401
1416
  }
1402
1417
  return out;
1403
1418
  }
1419
+ function termHasTripleTerm(term) {
1420
+ if (!term) return false;
1421
+ if (term.kind === 'triple') return true;
1422
+ if (term.kind === 'list') return term.items.some(termHasTripleTerm);
1423
+ if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
1424
+ return false;
1425
+ }
1426
+ function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
1427
+ function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
1428
+ function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
1404
1429
  function trigGraphBlock(label, atoms) {
1405
1430
  const lines = [label + ' {'];
1406
1431
  for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
@@ -1448,7 +1473,8 @@ function formalOutputToTrig(facts, trig) {
1448
1473
  const prefixes = prefixLinesFromTrig(trig);
1449
1474
  if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
1450
1475
  const nl = String.fromCharCode(10);
1451
- return prefixes.join(nl) + nl + nl + body.join(nl);
1476
+ const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
1477
+ return version + prefixes.join(nl) + nl + nl + body.join(nl);
1452
1478
  }
1453
1479
  function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
1454
1480
  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) {
@@ -1981,7 +1996,7 @@ const QUERIES = [
1981
1996
  ]
1982
1997
  }
1983
1998
  ];
1984
- const DOC_MARKDOWN = "# Fundamental Theorem Arithmetic\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the hand-written SEE example. The primary case mirrors\nEyeling's fundamental-theorem-arithmetic.n3: n = 202692987 is validated against\nthe factorization 3^2 * 7 * 829 * 3881. Extra sample summaries are kept as\ndata so the SEE report still documents the larger regression set.\n\n## Compilation summary\n\n- Example name: `fundamental_theorem_arithmetic`\n- Input facts emitted: 16\n- Forward rules compiled: 4\n- Backward predicate rules compiled: 0\n- Fuses compiled: 3\n- Predicate count: 25\n\n## Built-ins used\n\n- `list:reverse`\n- `list:sort`\n- `log:equalTo`\n- `log:notIncludes`\n- `log:outputString`\n- `math:equalTo`\n- `math:product`\n- `string:format`\n\n## Runtime model\n\nThe generated `examples/fundamental_theorem_arithmetic.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/fundamental_theorem_arithmetic.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/fundamental-theorem-arithmetic.trig` or `examples/input/fundamental_theorem_arithmetic.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/fundamental_theorem_arithmetic.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";
1999
+ const DOC_MARKDOWN = "# Fundamental Theorem Arithmetic\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the hand-written SEE example. The primary case mirrors\nEyeling's fundamental-theorem-arithmetic.n3: n = 202692987 is validated against\nthe factorization 3^2 * 7 * 829 * 3881. Extra sample summaries are kept as\ndata so the SEE report still documents the larger regression set.\n\n## Compilation summary\n\n- Example name: `fundamental_theorem_arithmetic`\n- Input facts emitted: 16\n- Forward rules compiled: 4\n- Backward predicate rules compiled: 0\n- Fuses compiled: 3\n- Predicate count: 25\n\n## Built-ins used\n\n- `list:reverse`\n- `list:sort`\n- `log:equalTo`\n- `log:notIncludes`\n- `log:outputString`\n- `math:equalTo`\n- `math:product`\n- `string:format`\n\n## Runtime model\n\nThe generated `examples/fundamental_theorem_arithmetic.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/fundamental_theorem_arithmetic.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/fundamental-theorem-arithmetic.trig` or `examples/input/fundamental_theorem_arithmetic.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/fundamental_theorem_arithmetic.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";
1985
2000
  function seeMetadata(data) { return (data && data.__see) || {}; }
1986
2001
  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 !== "d88eca366c590d49544e49453cbe69ba38938f8bc23919278bfaadd0bed0cd2d") 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 === "d88eca366c590d49544e49453cbe69ba38938f8bc23919278bfaadd0bed0cd2d", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 7, '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 }; }
1987
2002
  function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
@@ -2015,6 +2030,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
2015
2030
  }
2016
2031
  return out;
2017
2032
  }
2033
+ function termHasTripleTerm(term) {
2034
+ if (!term) return false;
2035
+ if (term.kind === 'triple') return true;
2036
+ if (term.kind === 'list') return term.items.some(termHasTripleTerm);
2037
+ if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
2038
+ return false;
2039
+ }
2040
+ function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
2041
+ function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
2042
+ function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
2018
2043
  function trigGraphBlock(label, atoms) {
2019
2044
  const lines = [label + ' {'];
2020
2045
  for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
@@ -2062,7 +2087,8 @@ function formalOutputToTrig(facts, trig) {
2062
2087
  const prefixes = prefixLinesFromTrig(trig);
2063
2088
  if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
2064
2089
  const nl = String.fromCharCode(10);
2065
- return prefixes.join(nl) + nl + nl + body.join(nl);
2090
+ const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
2091
+ return version + prefixes.join(nl) + nl + nl + body.join(nl);
2066
2092
  }
2067
2093
  function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
2068
2094
  const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);