skynet-graph 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/CONTRIBUTING.md +22 -0
  2. package/LICENSE +661 -0
  3. package/README.md +245 -0
  4. package/bin/sg +20 -0
  5. package/concepts/README.md +23 -0
  6. package/concepts/_substrate/Claim/Confidence/Trusted.json +6 -0
  7. package/concepts/_substrate/Claim/Confidence.json +10 -0
  8. package/concepts/_substrate/Claim/Freshness/Stale.json +7 -0
  9. package/concepts/_substrate/Claim/Freshness.json +5 -0
  10. package/concepts/_substrate/Claim/Verification/Refuted.json +6 -0
  11. package/concepts/_substrate/Claim/Verification.json +6 -0
  12. package/concepts/_substrate/Claim.json +4 -0
  13. package/concepts/_substrate/Frontier/InBeam.json +6 -0
  14. package/concepts/_substrate/Frontier/Stuck.json +6 -0
  15. package/concepts/_substrate/Frontier.json +5 -0
  16. package/concepts/_substrate/Intake/ToTask.json +22 -0
  17. package/concepts/_substrate/Intake/Typed.json +10 -0
  18. package/concepts/_substrate/Intake.json +27 -0
  19. package/concepts/_substrate/Task/Answer.json +12 -0
  20. package/concepts/_substrate/Task/Complexity/Atomic.json +5 -0
  21. package/concepts/_substrate/Task/Complexity/Compound/Expansion.json +7 -0
  22. package/concepts/_substrate/Task/Complexity/Compound.json +6 -0
  23. package/concepts/_substrate/Task/Complexity.json +6 -0
  24. package/concepts/_substrate/Task/EvalComplexity.json +13 -0
  25. package/concepts/_substrate/Task/ReportUp.json +6 -0
  26. package/concepts/_substrate/Task/Rollup.json +10 -0
  27. package/concepts/_substrate/Task.json +5 -0
  28. package/concepts/clinical/Diagnosis/Medication.json +6 -0
  29. package/concepts/clinical/Diagnosis.json +15 -0
  30. package/concepts/clinical/Observation/LabValue/OutOfRange.json +6 -0
  31. package/concepts/clinical/Observation/LabValue.json +5 -0
  32. package/concepts/clinical/Observation.json +6 -0
  33. package/concepts/common/Document/pathBasket.json +6 -0
  34. package/concepts/common/Document.json +3 -0
  35. package/concepts/common/Edge/Distance.json +13 -0
  36. package/concepts/common/Edge/Stay/LongStay.json +8 -0
  37. package/concepts/common/Edge/Stay.json +8 -0
  38. package/concepts/common/Edge/Travel/LongTravel.json +16 -0
  39. package/concepts/common/Edge/Travel/ShortTravel.json +11 -0
  40. package/concepts/common/Edge/Travel/targetNode.json +7 -0
  41. package/concepts/common/Edge/Travel.json +7 -0
  42. package/concepts/common/Edge.json +3 -0
  43. package/concepts/common/Vertice.json +3 -0
  44. package/concepts/supply/Fulfillment.json +10 -0
  45. package/concepts/supply/Inventory/Reorder.json +7 -0
  46. package/concepts/supply/Inventory.json +5 -0
  47. package/concepts/supply/Procurement/SupplierConfirm.json +6 -0
  48. package/concepts/supply/Procurement.json +8 -0
  49. package/concepts/supply/Transport.json +5 -0
  50. package/doc/API.md +556 -0
  51. package/doc/CAPABILITIES.md +373 -0
  52. package/doc/MODELISATION.md +511 -0
  53. package/doc/architecture.md +267 -0
  54. package/doc/concept-as-graph.md +379 -0
  55. package/doc/concept-learning.md +168 -0
  56. package/doc/creative-loop-map.md +202 -0
  57. package/doc/original-2016-doc.md +148 -0
  58. package/doc/usage.md +501 -0
  59. package/lib/authoring/abstract.js +401 -0
  60. package/lib/authoring/abstraction.js +190 -0
  61. package/lib/authoring/adapt.js +393 -0
  62. package/lib/authoring/ancestry.js +216 -0
  63. package/lib/authoring/author.js +147 -0
  64. package/lib/authoring/bounded-merge.js +57 -0
  65. package/lib/authoring/canon.js +158 -0
  66. package/lib/authoring/clock.js +123 -0
  67. package/lib/authoring/combinator.js +92 -0
  68. package/lib/authoring/compete.js +76 -0
  69. package/lib/authoring/compose-hotspot.js +314 -0
  70. package/lib/authoring/compress.js +279 -0
  71. package/lib/authoring/concept-net.js +254 -0
  72. package/lib/authoring/concepts.js +66 -0
  73. package/lib/authoring/context-project.js +277 -0
  74. package/lib/authoring/contract.js +476 -0
  75. package/lib/authoring/corpus-pack.js +125 -0
  76. package/lib/authoring/cost-probe.js +86 -0
  77. package/lib/authoring/crystallize.js +555 -0
  78. package/lib/authoring/dag-decompose.js +256 -0
  79. package/lib/authoring/dataset-adapter.js +253 -0
  80. package/lib/authoring/debug-provider.js +92 -0
  81. package/lib/authoring/decompose.js +343 -0
  82. package/lib/authoring/emittability.js +236 -0
  83. package/lib/authoring/equilibrium.js +160 -0
  84. package/lib/authoring/extract.js +153 -0
  85. package/lib/authoring/forest.js +55 -0
  86. package/lib/authoring/forge-fallback.js +116 -0
  87. package/lib/authoring/givens.js +121 -0
  88. package/lib/authoring/glossary.js +177 -0
  89. package/lib/authoring/grammar-graph.js +191 -0
  90. package/lib/authoring/granularity.js +80 -0
  91. package/lib/authoring/graph-net.js +108 -0
  92. package/lib/authoring/ground.js +148 -0
  93. package/lib/authoring/higher-order.js +85 -0
  94. package/lib/authoring/hotspot.js +132 -0
  95. package/lib/authoring/hysteresis.js +85 -0
  96. package/lib/authoring/index.js +106 -0
  97. package/lib/authoring/lattice-morphism.js +61 -0
  98. package/lib/authoring/lattice-pack.js +148 -0
  99. package/lib/authoring/leaf-io.js +77 -0
  100. package/lib/authoring/library.js +156 -0
  101. package/lib/authoring/lifecycle.js +92 -0
  102. package/lib/authoring/loop.js +193 -0
  103. package/lib/authoring/master-loop.js +132 -0
  104. package/lib/authoring/mdl.js +209 -0
  105. package/lib/authoring/memo-stability.js +122 -0
  106. package/lib/authoring/method-explorer.js +138 -0
  107. package/lib/authoring/method-pack.js +181 -0
  108. package/lib/authoring/method.js +202 -0
  109. package/lib/authoring/mine.js +510 -0
  110. package/lib/authoring/mount.js +119 -0
  111. package/lib/authoring/negotiate.js +100 -0
  112. package/lib/authoring/parametric.js +139 -0
  113. package/lib/authoring/reaggregate.js +75 -0
  114. package/lib/authoring/rebalance.js +157 -0
  115. package/lib/authoring/recall.js +135 -0
  116. package/lib/authoring/registry.js +350 -0
  117. package/lib/authoring/relearn.js +147 -0
  118. package/lib/authoring/retention.js +89 -0
  119. package/lib/authoring/segment-proxy.js +114 -0
  120. package/lib/authoring/serve-leaf.js +64 -0
  121. package/lib/authoring/slot-aware-serve.js +77 -0
  122. package/lib/authoring/sound-invoke.js +59 -0
  123. package/lib/authoring/split-serve.js +74 -0
  124. package/lib/authoring/ste.js +239 -0
  125. package/lib/authoring/stock.js +101 -0
  126. package/lib/authoring/store.js +94 -0
  127. package/lib/authoring/supervise.js +138 -0
  128. package/lib/authoring/support.js +131 -0
  129. package/lib/authoring/task-mirror.js +51 -0
  130. package/lib/authoring/typed-loop.js +160 -0
  131. package/lib/authoring/validate.js +569 -0
  132. package/lib/authoring/widen.js +88 -0
  133. package/lib/combos/appliance.js +189 -0
  134. package/lib/combos/critique.js +377 -0
  135. package/lib/combos/defaults.js +86 -0
  136. package/lib/combos/durable-runner.js +115 -0
  137. package/lib/combos/forge.js +209 -0
  138. package/lib/combos/index.js +86 -0
  139. package/lib/combos/learning-library.js +253 -0
  140. package/lib/combos/mixture-serve.js +193 -0
  141. package/lib/combos/plan-loop.js +192 -0
  142. package/lib/combos/proxy-cache.js +272 -0
  143. package/lib/combos/self-mod.js +94 -0
  144. package/lib/durable/audit.js +102 -0
  145. package/lib/durable/checkpoint-store.js +550 -0
  146. package/lib/durable/fold.js +62 -0
  147. package/lib/durable/interpreter.js +242 -0
  148. package/lib/durable/xlate.js +185 -0
  149. package/lib/graph/Graph.js +2486 -0
  150. package/lib/graph/expr.js +307 -0
  151. package/lib/graph/index.js +26 -0
  152. package/lib/graph/log.js +134 -0
  153. package/lib/graph/objects/Concept.js +382 -0
  154. package/lib/graph/objects/Entity.js +503 -0
  155. package/lib/graph/objects/Node.js +57 -0
  156. package/lib/graph/objects/PathMap.js +557 -0
  157. package/lib/graph/objects/Segment.js +100 -0
  158. package/lib/graph/tasks/stabilize.js +59 -0
  159. package/lib/graph/tasks/taskflow.js +247 -0
  160. package/lib/index.js +122 -0
  161. package/lib/load.js +132 -0
  162. package/lib/providers/backends.js +117 -0
  163. package/lib/providers/borderline.js +129 -0
  164. package/lib/providers/cache.js +226 -0
  165. package/lib/providers/canonicalize.js +198 -0
  166. package/lib/providers/constat.js +107 -0
  167. package/lib/providers/geo.js +53 -0
  168. package/lib/providers/index.js +116 -0
  169. package/lib/providers/intake.js +263 -0
  170. package/lib/providers/llm-local.js +88 -0
  171. package/lib/providers/llm.js +236 -0
  172. package/lib/providers/local-host.js +197 -0
  173. package/lib/providers/merge-consistency.js +162 -0
  174. package/lib/providers/nogood.js +149 -0
  175. package/lib/providers/reason-loop.js +223 -0
  176. package/lib/providers/semiring.js +285 -0
  177. package/lib/providers/solver-fork.js +163 -0
  178. package/lib/providers/stats.js +228 -0
  179. package/lib/providers/verify.js +120 -0
  180. package/lib/runtime/index.js +135 -0
  181. package/lib/runtime/invoke-pool.js +92 -0
  182. package/lib/runtime/protocol.js +210 -0
  183. package/lib/runtime/transport-socket.js +77 -0
  184. package/lib/runtime/worker-entry.js +31 -0
  185. package/lib/sg/cli.js +601 -0
  186. package/lib/sg/log-sinks.js +214 -0
  187. package/lib/sg/mcp.js +395 -0
  188. package/lib/sg/proxy-run.js +66 -0
  189. package/lib/sg/serve-demo.js +185 -0
  190. package/lib/sg/serve.js +205 -0
  191. package/lib/sg/trace.js +118 -0
  192. package/lib/studio/protocol.js +45 -0
  193. package/lib/studio/public/app.js +310 -0
  194. package/lib/studio/public/components/ConceptEditor.js +43 -0
  195. package/lib/studio/public/components/ConceptTree.js +43 -0
  196. package/lib/studio/public/components/CorpusPanel.js +68 -0
  197. package/lib/studio/public/components/ForkTree.js +36 -0
  198. package/lib/studio/public/components/GrammarGraph.js +99 -0
  199. package/lib/studio/public/components/GraphCanvas.js +156 -0
  200. package/lib/studio/public/components/Inspector.js +48 -0
  201. package/lib/studio/public/components/LearningPanel.js +85 -0
  202. package/lib/studio/public/components/PromptConsole.js +46 -0
  203. package/lib/studio/public/components/ProviderTrace.js +39 -0
  204. package/lib/studio/public/components/SessionSplit.js +47 -0
  205. package/lib/studio/public/components/TilingOverlay.js +40 -0
  206. package/lib/studio/public/components/Timeline.js +57 -0
  207. package/lib/studio/public/index.html +26 -0
  208. package/lib/studio/public/styles.css +185 -0
  209. package/lib/studio/public/ws.js +58 -0
  210. package/lib/studio/server.js +130 -0
  211. package/lib/studio/session.js +397 -0
  212. package/lib/studio/studio.js +149 -0
  213. package/package.json +42 -0
@@ -0,0 +1,101 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * AGPL-3.0-or-later. See <https://www.gnu.org/licenses/>.
6
+ */
7
+ 'use strict';
8
+ /**
9
+ * stock — the reusable CORE of gold-gated concept-method STOCK-BUILDING (host-side, ZERO-CORE, engine-free).
10
+ * The WikiSQL pilot (2026-07-05) proved a stock of .sgc methods can be accumulated from a REAL externally-
11
+ * labelled corpus: `record → forge model decomposition → crystallizeFrom → GOLD-GATE → .sgc`. That pilot
12
+ * inlined the gate + the yield lever; THIS brick promotes them to the library so any dataset adapter reuses
13
+ * them — a host writes only the small per-dataset `{ sigOf, goldStepsOf, problemOf }` adapter + a forge, and
14
+ * the SOUNDNESS-bearing pieces (the gate that keeps the stock clean, the packaging) live here, tested once.
15
+ *
16
+ * The two findings the pilot measured, both captured here:
17
+ * 1. THE VALUE IS THE GATE, not the generation. `goldGate` admits a class method into the stock ONLY IF
18
+ * the model's decomposition is CONSISTENT across the class's instances AND MATCHES the gold shape AND
19
+ * crystallize admitted it (K1-sound). So the stock stays 100% clean (0 false admitted) whatever the
20
+ * small model renders — the oracle (the dataset's gold) + the gate carry soundness, not the model.
21
+ * 2. YIELD IS BOUNDED BY MODEL CONSISTENCY (the pilot's small model omitted/inconsistently-added a step).
22
+ * `consistencyVote` is the cheap, SOUNDNESS-PRESERVING lever: sample N decompositions of one instance,
23
+ * take the majority shape. It raises the fraction of instances that reach a consistent shape WITHOUT
24
+ * bypassing the gate (the gate still verifies the voted shape against gold) — never a soundness risk.
25
+ *
26
+ * Pure functions over shapes (arrays of typed step kinds) — NO engine, NO fs, NO model. The trace-producing
27
+ * run (a Plan decomposition through the engine) stays the host's; this brick verifies + packages its output.
28
+ */
29
+
30
+ const packMethods = require('./method-pack.js').packMethods;
31
+
32
+ /** the canonical SHAPE of a typed decomposition — the ordered step kinds joined (the class-method identity). */
33
+ function shapeOf( steps ) { return (steps || []).join('>'); }
34
+
35
+ /**
36
+ * CONSISTENCY VOTE (the yield lever) — given N sampled decompositions of the SAME instance, return the
37
+ * MAJORITY shape + the agreement fraction. Deterministic on ties (first-seen mode wins). Empty samples vote
38
+ * to an empty shape with agreement 0. Soundness-preserving: the voted shape is still gold-gated downstream.
39
+ * @param samples array of step-arrays (each a decomposition of one instance).
40
+ * @returns {{ steps, shape, agreement, votes:{ <shape>: count }, n }}
41
+ */
42
+ function consistencyVote( samples ) {
43
+ samples = (samples || []).filter( Array.isArray );
44
+ const votes = {}, first = {};
45
+ let bestShape = '', bestCount = 0;
46
+ for ( let i = 0; i < samples.length; i++ ) {
47
+ const sh = shapeOf(samples[i]);
48
+ if ( !sh ) continue; // an empty decomposition is not a vote
49
+ votes[sh] = (votes[sh] || 0) + 1;
50
+ if ( first[sh] === undefined ) first[sh] = i;
51
+ if ( votes[sh] > bestCount || (votes[sh] === bestCount && first[sh] < first[bestShape]) ) { bestShape = sh; bestCount = votes[sh]; }
52
+ }
53
+ const n = samples.length;
54
+ const steps = bestShape ? bestShape.split('>') : [];
55
+ return { steps, shape: bestShape, agreement: n ? bestCount / n : 0, votes, n };
56
+ }
57
+
58
+ /**
59
+ * THE GOLD-GATE — the verification rule that keeps a stock clean. Admit a class method into the stock iff:
60
+ * (a) the model's per-instance shapes are CONSISTENT (all equal + non-empty across the class's instances),
61
+ * (b) that shape MATCHES the gold shape (the dataset oracle), and
62
+ * (c) crystallize ADMITTED the method (K1-sound: the trace carried typed content, not free prose).
63
+ * By construction it NEVER admits a shape≠gold method — the stock's 0-false property is structural, not
64
+ * model-dependent. Reports a discriminating `reason` on refusal (for the LOG / a curator).
65
+ * @param opts { modelShapes:[…], goldSteps:[…]|goldShape:string, crystallized:bool }
66
+ * @returns {{ admitted, consistent, goldMatch, crystallized, modelShape, goldShape, reason }}
67
+ */
68
+ function goldGate( opts ) {
69
+ opts = opts || {};
70
+ const modelShapes = (opts.modelShapes || []).map( ( s ) => Array.isArray(s) ? shapeOf(s) : String(s || '') );
71
+ const goldShape = opts.goldShape != null ? opts.goldShape : shapeOf(opts.goldSteps);
72
+ const crystallized = !!opts.crystallized;
73
+ const consistent = modelShapes.length > 0 && modelShapes.every( ( s ) => s === modelShapes[0] && s.length > 0 );
74
+ const modelShape = consistent ? modelShapes[0] : (modelShapes[0] || '');
75
+ const goldMatch = consistent && modelShape === goldShape;
76
+ const admitted = consistent && goldMatch && crystallized;
77
+ const reason = admitted ? 'admit'
78
+ : !consistent ? 'model-inconsistent'
79
+ : !goldMatch ? 'shape-mismatches-gold'
80
+ : 'crystallize-refused';
81
+ return { admitted, consistent, goldMatch, crystallized, modelShape, goldShape, reason };
82
+ }
83
+
84
+ /**
85
+ * Pack the ADMITTED class methods into a portable `.sgc` stock (thin over method-pack). Each admitted class
86
+ * is a recall-index entry keyed on its class signature (structure = `{ taskKind: sig }`).
87
+ * @param admitted array of `{ sig, candidate }` (the gold-verified class methods).
88
+ * @param opts { name, version, description, structureKey } — structureKey defaults to 'taskKind'.
89
+ * @returns a `.sgc kind:'methods'` bundle.
90
+ */
91
+ function packStock( admitted, opts ) {
92
+ opts = opts || {};
93
+ const sk = opts.structureKey || 'taskKind';
94
+ const entries = (admitted || []).filter( ( a ) => a && a.candidate ).map( ( a ) => {
95
+ const structure = {}; structure[sk] = a.sig;
96
+ return { structure, content: {}, method: a.candidate };
97
+ } );
98
+ return packMethods({ entries: entries }, { name: opts.name || 'stock', version: opts.version || '0.0.0', description: opts.description || '' });
99
+ }
100
+
101
+ module.exports = { shapeOf, consistencyVote, goldGate, packStock };
@@ -0,0 +1,94 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * AGPL-3.0-or-later. See <https://www.gnu.org/licenses/>.
6
+ */
7
+ 'use strict';
8
+ /**
9
+ * store — DISK-BACKED PERSISTENCE for the method library (host-side, opt-in; uses fs, like `lib/load.js` —
10
+ * the engine core stays fs-free). M2 of the productization campaign: the derivation cache `store` and the
11
+ * master-loop `cache` are both pluggable Map-likes, so a file-backed store makes the warm library SURVIVE a
12
+ * restart — a cold process re-loads it and replays recurrent methods at 0 model calls (cross-session
13
+ * amortization, the master-graph study's persistence rung U5).
14
+ *
15
+ * const store = createFileStore('./.sg-cache.json');
16
+ * const cache = createProviderCache({ store }); // the derivation cache persists
17
+ * const loop = createMasterLoop({ ..., cache: store }); // or the master-loop cache persists
18
+ *
19
+ * It is a write-through Map: load on construct, persist the whole map on each mutation (simple + correct at
20
+ * reasoning-grade volume; swap in an append-log/LRU for high throughput). Values must be JSON-serializable
21
+ * (the cached templates/results already are — they round-trip through `serialize`/the rev-log).
22
+ */
23
+ const fs = require('fs');
24
+
25
+ function createFileStore( file, opts ) {
26
+ opts = opts || {};
27
+ const max = opts.max || 0; // 0 = unbounded; else oldest-eviction like createProviderCache
28
+ let map = new Map();
29
+ // LOAD: re-hydrate the library from disk (a "restart"). A missing/corrupt file → empty (fail-open).
30
+ try {
31
+ if ( fs.existsSync(file) ) {
32
+ const arr = JSON.parse(fs.readFileSync(file, 'utf8'));
33
+ if ( Array.isArray(arr) ) map = new Map(arr);
34
+ }
35
+ } catch ( e ) { map = new Map(); }
36
+
37
+ let timer = null;
38
+ function persist() {
39
+ // debounce-free, synchronous write-through (deterministic for tests); coalesce via opts.async if wanted.
40
+ const data = JSON.stringify([...map.entries()]);
41
+ if ( opts.async ) { clearTimeout(timer); timer = setTimeout(() => { try { fs.writeFileSync(file, data); } catch ( e ) {} }, opts.async); }
42
+ else try { fs.writeFileSync(file, data); } catch ( e ) {}
43
+ }
44
+ function evict() { if ( max && map.size > max ) map.delete(map.keys().next().value); }
45
+
46
+ return {
47
+ has( k ) { return map.has(k); },
48
+ get( k ) { return map.get(k); },
49
+ set( k, v ) { map.set(k, v); evict(); persist(); return this; },
50
+ delete( k ) { const r = map.delete(k); persist(); return r; },
51
+ clear() { map.clear(); persist(); },
52
+ keys() { return map.keys(); },
53
+ get size() { return map.size; },
54
+ flush() { const data = JSON.stringify([...map.entries()]); try { fs.writeFileSync(file, data); } catch ( e ) {} },
55
+ file
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Persist / re-hydrate a recall INDEX (so fuzzy recall + partial reuse also survive a restart). The index
61
+ * stores { sig, method } entries (the vectors are recomputed on load — they are a pure function of sig).
62
+ */
63
+ function saveIndex( index, file ) {
64
+ const entries = (index.entries || []).map(( e ) => ({ sig: e.sig, method: e.method }));
65
+ try { fs.writeFileSync(file, JSON.stringify(entries)); } catch ( e ) {}
66
+ return entries.length;
67
+ }
68
+ function loadIndex( index, file ) {
69
+ try {
70
+ if ( !fs.existsSync(file) ) return 0;
71
+ const arr = JSON.parse(fs.readFileSync(file, 'utf8'));
72
+ for ( const e of (Array.isArray(arr) ? arr : []) ) index.add(e.sig, e.method);
73
+ return arr.length;
74
+ } catch ( e ) { return 0; }
75
+ }
76
+
77
+ /**
78
+ * Generic `.sgc` bundle file IO (works for BOTH a corpus-pack and a method-pack bundle — anything with
79
+ * `format:'sgc'`). The pack modules stay fs-free (they return/consume plain bundle objects); the disk
80
+ * round-trip lives here, the fs layer. `saveSgc` writes pretty JSON (a `.sgc` is meant to be inspected/
81
+ * diffed); `loadSgc` returns the parsed bundle or null on a missing/corrupt file (fail-open).
82
+ */
83
+ function saveSgc( bundle, file ) {
84
+ try { fs.writeFileSync(file, JSON.stringify(bundle, null, 2)); return true; } catch ( e ) { return false; }
85
+ }
86
+ function loadSgc( file ) {
87
+ try {
88
+ if ( !fs.existsSync(file) ) return null;
89
+ const b = JSON.parse(fs.readFileSync(file, 'utf8'));
90
+ return (b && b.format === 'sgc') ? b : null;
91
+ } catch ( e ) { return null; }
92
+ }
93
+
94
+ module.exports = { createFileStore, saveIndex, loadIndex, saveSgc, loadSgc };
@@ -0,0 +1,138 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ 'use strict';
19
+ /**
20
+ * Hypothesis-and-test self-modification regime (roadmap #11.c.3, doc/MODELISATION.md
21
+ * §6.4) — the capstone of #11. Zero core change: it composes the now-complete safe
22
+ * instruments — re-entrant `add`/`patchConcept` (#11.a), the apply-ceiling backstop
23
+ * (#11.c.1), and concept-lib-versioned `rollbackTo` (#11.c.2 / N6) — into a supervised
24
+ * loop that may *try* a self-modification and cleanly *undo* it if it doesn't help.
25
+ *
26
+ * Design (per the engine author):
27
+ * - `Stuck` is a human-vocabulary fact (a sub-problem that exhausted its strategies /
28
+ * blew its budget) — the loop's TRIGGER, surfaced by `detectStuck`;
29
+ * - the "is it better / worse?" call is NOT an engine metric. Budget-spent is only the
30
+ * trigger; the judgment is a *supervisor* decision rendered by a higher-level concept
31
+ * with a better model → injected as `evaluate` (the better-model hook);
32
+ * - the repair STRATEGY is open R&D / plural → injected as `hypothesize`. Different
33
+ * strategies plug in here without touching the loop.
34
+ *
35
+ * The loop is HOST-orchestrated because `rollbackTo` must run at a quiescent boundary
36
+ * (it re-mounts; it is not queued like add/patchConcept). A fully-reactive supervisor
37
+ * concept (with queued rollback) is the next R&D step.
38
+ *
39
+ * const { supervise } = require('./supervise');
40
+ * const res = await supervise(graph, {
41
+ * detectStuck: (g) => <stuckId|null>, // the trigger (Stuck fact / budget)
42
+ * hypothesize: async (g, stuckId, ctx) => { ...self-mod...; return <label> }, // a strategy
43
+ * evaluate: async (g, stuckId, hyp) => ({ better: <bool>, ... }), // the supervisor model
44
+ * maxAttempts: 5,
45
+ * });
46
+ * // res = { resolved, attempts: [{ hyp, outcome:'kept'|'reverted', verdict }] }
47
+ */
48
+
49
+ // Await the next settle, or resolve immediately if the graph is already quiescent
50
+ // (finding #13: `stabilize(cb)` only fires `cb` on an actual settle — a no-op has none).
51
+ function nextStable( graph ) {
52
+ return new Promise(function ( resolve ) {
53
+ var done = false;
54
+ var fin = function () { if ( !done ) { done = true; resolve(); } };
55
+ graph.stabilize(fin);
56
+ if ( !graph._unstable.length && !graph._triggeredCastCount ) setTimeout(fin);
57
+ });
58
+ }
59
+
60
+ async function supervise( graph, spec ) {
61
+ var detectStuck = spec.detectStuck;
62
+ var hypothesize = spec.hypothesize;
63
+ var evaluate = spec.evaluate;
64
+ var maxAttempts = spec.maxAttempts || 5;
65
+ var attempts = [];
66
+
67
+ await nextStable(graph);
68
+
69
+ for ( var i = 0; i < maxAttempts; i++ ) {
70
+ var stuck = detectStuck(graph);
71
+ if ( !stuck ) return { resolved: true, attempts: attempts };
72
+
73
+ // checkpoint BEFORE the hypothesis: the snapshot captured at the last settle, keyed
74
+ // by this rev, carries both the facts AND the concept-lib (N6) — so a rollback here
75
+ // fully undoes whatever the hypothesis adds/patches.
76
+ var rev = graph.getCurrentRevision();
77
+
78
+ var hyp = await hypothesize(graph, stuck, { attempt: i, attempts: attempts });
79
+ await nextStable(graph); // apply the hypothesis (its self-mod re-stabilizes)
80
+
81
+ var verdict = await evaluate(graph, stuck, hyp);
82
+ if ( verdict && verdict.better ) {
83
+ attempts.push({ hyp: hyp, outcome: 'kept', verdict: verdict });
84
+ } else {
85
+ graph.rollbackTo(rev); // safe at this quiescent boundary; N6 restores rules too
86
+ await nextStable(graph);
87
+ attempts.push({ hyp: hyp, outcome: 'reverted', verdict: verdict });
88
+ }
89
+ }
90
+
91
+ return { resolved: !detectStuck(graph), attempts: attempts };
92
+ }
93
+
94
+ /**
95
+ * The supervisor as a REACTIVE concept flow (vs the host-orchestrated `supervise` loop).
96
+ * Enabled by queued rollback (#11.c.4): a concept's provider can now trigger a rollback
97
+ * mid-stabilize and it defers safely. The flow:
98
+ * Stuck ──require──▶ Supervise (hypothesizes a self-mod, records a rollback checkpoint)
99
+ * Supervise+hypothesized ──require──▶ Evaluate (the supervisor/better-model verdict)
100
+ * Evaluate, ensure verdict=='worse' ──▶ Revert (queued rollbackTo the checkpoint)
101
+ *
102
+ * Inject the policy (open R&D):
103
+ * opts.hypothesize(graph, scope) — apply a self-mod; the added/patched fix MUST post
104
+ * `hypothesized: true` so Evaluate can fire after it lands
105
+ * (and `resolved: true` + `Stuck: null` if it solves it).
106
+ * opts.evaluate(graph, scope) — return 'better' | 'worse' (the supervisor judgment).
107
+ *
108
+ * NOTE (open R&D): the rollback checkpoint is `graph._lastSettledRev` — the last clean
109
+ * snapshot. A robust multi-attempt reactive loop (a checkpoint that holds the problem but
110
+ * not the attempt, + strategy memory so retries differ) is the next research step; this
111
+ * tree demonstrates the reactive wiring and the queued-rollback Revert path.
112
+ */
113
+ function reactiveSupervisorTree() {
114
+ return { childConcepts: {
115
+ Supervise: { _id: 'Supervise', _name: 'Supervise', require: ['Stuck'], provider: ['Sup::propose'] },
116
+ Evaluate: { _id: 'Evaluate', _name: 'Evaluate', require: ['Supervise', 'hypothesized'], provider: ['Sup::judge'] },
117
+ Revert: { _id: 'Revert', _name: 'Revert', require: ['Evaluate'], ensure: ["$verdict=='worse'"], provider: ['Sup::revert'] }
118
+ } };
119
+ }
120
+
121
+ function makeSupervisorProviders( opts ) {
122
+ var hypothesize = opts.hypothesize, evaluate = opts.evaluate;
123
+ return { Sup: {
124
+ propose: function ( graph, concept, scope, argz, cb ) {
125
+ hypothesize(graph, scope);// a self-mod (queued); its fix posts `hypothesized:true`
126
+ cb(null, { $_id: '_parent', Supervise: true, supRev: graph._lastSettledRev });
127
+ },
128
+ judge: function ( graph, concept, scope, argz, cb ) {
129
+ cb(null, { $_id: '_parent', Evaluate: true, verdict: evaluate(graph, scope) });
130
+ },
131
+ revert: function ( graph, concept, scope, argz, cb ) {
132
+ graph.rollbackTo(scope._.supRev);// queued (#11.c.4) -> applied at the quiescent boundary
133
+ cb(null, { $_id: '_parent', Revert: true });
134
+ }
135
+ } };
136
+ }
137
+
138
+ module.exports = { supervise, nextStable, reactiveSupervisorTree, makeSupervisorProviders };
@@ -0,0 +1,131 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ 'use strict';
19
+ /**
20
+ * The SUPPORT GRAMMAR (host-side authoring brick) — the support-grammar study made runnable
21
+ * (doc/WIP/studies/2026-06-25-grammaire-de-support-resolution-probleme.md). It composes two
22
+ * already-shipped pieces so a SMALL model only has to be locally competent on a bounded segment
23
+ * while the GRAPH holds the structure and runs the search:
24
+ *
25
+ * (structure, J1) the decompose loop (lib/authoring/loop.js): a Task DAG that splits a problem
26
+ * into segments and synthesizes bottom-up (reactive Rollup) — the whole is NEVER in the
27
+ * model's context, only `${label}` of one segment;
28
+ * (alternatives, J2) the per-segment SELECT cluster (lib/providers/semiring.js#selectConceptTree):
29
+ * an atomic segment GENERATES several candidate answers (`Propose`), the `pareto` semiring
30
+ * keeps the non-dominated trade-offs and picks one (`Select`), and `Adopt` makes the selected
31
+ * candidate the segment's answer. Multi-criteria selection is reified in the graph (not flattened
32
+ * by the model), keyed on DISCRETE bands (barrier-clean, deterministic).
33
+ *
34
+ * const { supportConceptTree, makeSupportProviders } = require('./support');
35
+ * const providers = makeSupportProviders({ evalFn, expandFn, proposeFn, rollupFn });
36
+ * register(Graph, [ providers ]); // or Graph._providers = {...}
37
+ * new Graph(seed, conf, { common: supportConceptTree({ criteria, lex }) });
38
+ *
39
+ * Content (eval/expand/propose/rollup) is INJECTED — deterministic in tests, an LLM in production.
40
+ * Zero core change: everything is concepts + providers over the existing engine.
41
+ */
42
+ const { makeDecomposeProviders } = require('./loop.js');
43
+ const { createSemiring, selectConceptTree } = require('../providers');
44
+ const { rankOf } = require('../providers/semiring.js');
45
+
46
+ /**
47
+ * The support-grammar concept set: the decompose scaffold + the per-segment Propose→Select→Adopt
48
+ * alternative-selection trio + reactive bottom-up synthesis.
49
+ * @param opts.criteria the SELECT comparison criteria (band lists or {dir}) — see semiring.js
50
+ * @param opts.lex criterion priority for the tie-break (default: criteria key order)
51
+ * @param opts.idKey candidate id key (default 'id')
52
+ */
53
+ function supportConceptTree( opts ) {
54
+ opts = opts || {};
55
+ var sel = selectConceptTree({
56
+ criteria: opts.criteria || {}, lex: opts.lex, idKey: opts.idKey,
57
+ contribKey: 'candidates', require: ['Task', 'Atomic']
58
+ });
59
+ return { childConcepts: { Task: {
60
+ _id: 'Task', _name: 'Task', require: 'Segment',
61
+ childConcepts: {
62
+ // structure (J1): classify, and split a compound segment into sub-segments
63
+ EvalComplexity: { _id: 'EvalComplexity', _name: 'EvalComplexity', require: ['Task'], provider: ['AI::evalComplexity'] },
64
+ Expand : { _id: 'Expand', _name: 'Expand', require: ['Task', 'NeedsSplit'], provider: ['AI::expand'] },
65
+ // alternatives (J2): an atomic segment proposes candidates, the pareto SELECT picks one
66
+ Propose : { _id: 'Propose', _name: 'Propose', require: ['Task', 'Atomic'], provider: ['Support::propose'] },
67
+ Select : sel.childConcepts.Select,
68
+ Adopt : { _id: 'Adopt', _name: 'Adopt', require: ['Task', 'selectedId'], provider: ['Support::adopt'] },
69
+ // reactive synthesis: report up to the parent, roll up once all children answered
70
+ ReportUp : { _id: 'ReportUp', _name: 'ReportUp', require: ['Task', 'Answered', 'parentSeg'], provider: ['AI::reportUp'] },
71
+ Rollup : { _id: 'Rollup', _name: 'Rollup', require: ['Task', 'expandedInto'],
72
+ ensure: ['$answeredBy.length == $expandedInto.length'], provider: ['AI::rollup'] }
73
+ }
74
+ } } };
75
+ }
76
+
77
+ /**
78
+ * The support-grammar providers. Reuses the decompose providers (AI::evalComplexity/expand/reportUp/
79
+ * rollup, lib/authoring/loop.js) + the pareto reducer (Semiring::reduce) and adds the two tiny
80
+ * Support providers (propose/adopt) that bracket the SELECT.
81
+ * @param opts.proposeFn(scope) -> [{ id, ...criteria, content }] candidate alternatives for a segment
82
+ * @param opts.escalateFn(scope) -> [{ id, ...criteria, content }] J3: a BETTER-tier proposer, called
83
+ * ONLY for a segment whose own candidates all fall below the bar (Stuck). The better model is
84
+ * spent only where the small one is locally insufficient.
85
+ * @param opts.escalateBar { criterion, order, min } the quality gate: escalate iff no candidate's
86
+ * `criterion` band reaches `min` (band order worst→best). Omit to disable escalation.
87
+ * (+ evalFn/expandFn/answerFn/rollupFn/maxDepth/maxBranch passed through to makeDecomposeProviders)
88
+ */
89
+ function makeSupportProviders( opts ) {
90
+ opts = opts || {};
91
+ var proposeFn = opts.proposeFn || function () { return []; },
92
+ escalateFn = opts.escalateFn,
93
+ bar = opts.escalateBar;
94
+ // does the candidate set already clear the quality bar? (a `min` band reached on `criterion`)
95
+ function clearsBar( cands ) {
96
+ if ( !bar ) return true;
97
+ var minRank = rankOf(bar.min, bar.order), best = -Infinity;
98
+ for ( var i = 0; i < cands.length; i++ ) best = Math.max(best, rankOf(cands[i][bar.criterion], bar.order));
99
+ return best >= minRank;
100
+ }
101
+ var support = { Support: {
102
+ // generate the segment's candidate alternatives (canned/LLM); if none clears the bar, ESCALATE
103
+ // to the better tier (J3) — recording the weak ones as memory — then arm the cardinality gate.
104
+ propose: function ( graph, concept, scope, argz, cb ) {
105
+ Promise.resolve(proposeFn(scope)).then(function ( cands ) {
106
+ cands = cands || [];
107
+ if ( escalateFn && bar && cands.length && !clearsBar(cands) ) {
108
+ return Promise.resolve(escalateFn(scope)).then(function ( better ) {
109
+ better = better || [];
110
+ var pool = cands.concat(better);
111
+ cb(null, { $_id: '_parent', Propose: true, Stuck: true, Escalated: true,
112
+ escalatedFrom: cands.map(function ( c ) { return c.id; }), // nogood memory of the dead-ends
113
+ candidates: pool, expected: pool.length });
114
+ });
115
+ }
116
+ cb(null, { $_id: '_parent', Propose: true, candidates: cands, expected: cands.length });
117
+ }).catch(function ( e ) {
118
+ cb(null, { $_id: '_parent', Propose: true, candidates: [], expected: 0, llmError: e.message });
119
+ });
120
+ },
121
+ // adopt the SELECTed candidate (by its discrete selectedId) as the segment's answer
122
+ adopt: function ( graph, concept, scope, argz, cb ) {
123
+ var id = scope._.selectedId, cands = scope._.candidates || [], sel = null;
124
+ for ( var i = 0; i < cands.length; i++ ) if ( cands[i].id === id ) { sel = cands[i]; break; }
125
+ cb(null, { $_id: '_parent', Adopt: true, Answered: true, answer: sel ? sel.content : '(none selected)' });
126
+ }
127
+ } };
128
+ return Object.assign({}, makeDecomposeProviders(opts), support, createSemiring(opts.semiringOpts));
129
+ }
130
+
131
+ module.exports = { supportConceptTree: supportConceptTree, makeSupportProviders: makeSupportProviders };
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * AGPL-3.0-or-later. See <https://www.gnu.org/licenses/>.
6
+ */
7
+ 'use strict';
8
+ /**
9
+ * task-mirror — the graph plan DRIVES the host agent's native task list (owner 2026-07-10). The graph instance is
10
+ * the authoritative multistep state (typed steps, needs/produces, frontier, JTMS); agent hosts (Claude Code,
11
+ * OpenCode, …) have their own task tools. MCP is host→server — a server cannot invoke host tools — so the correct
12
+ * realization is a TYPED DELTA the host applies VERBATIM to its own task system, AT ITS OWN CHOICE (SOFT lane,
13
+ * pull-based: the LLM calls the sync when it wants a mirror, or never).
14
+ *
15
+ * `diffPlanToTaskOps(plan, mirror)` → { ops, mirror } — pure, deterministic (id-ordered), idempotent (same state
16
+ * twice → empty delta). Neutral op vocabulary, host-agnostic:
17
+ * {op:'create', id, subject, blockedBy[]} a new step (blockedBy = its still-open needs)
18
+ * {op:'update', id, subject} the step title changed
19
+ * {op:'complete',id} the step reached done (a GATE-ADMITTED result, not a claim)
20
+ * {op:'reopen', id, reason} ⟐ the JTMS differentiator: a completed step whose premise DRIFTED
21
+ * was retracted → the mirrored task must reopen. No host does this.
22
+ * A step that VANISHES from the plan emits nothing (the mirror keeps it; the host decides its own hygiene).
23
+ *
24
+ * @param plan { steps: [{ id, title, needs: [ids], status: 'open'|'done'|'retracted', reason? }] }
25
+ * @param mirror the previous return's `mirror` (or null on first sync).
26
+ * @returns { ops: [...], mirror } — feed `mirror` back on the next call.
27
+ */
28
+ function diffPlanToTaskOps( plan, mirror ) {
29
+ var steps = (plan && plan.steps || []).slice().sort(function ( a, b ) { return a.id < b.id ? -1 : 1; });
30
+ var prev = (mirror && mirror.tasks) || {};
31
+ var ops = [], tasks = {};
32
+ var open = {}; steps.forEach(function ( s ) { if ( s.status !== 'done' ) open[s.id] = true; });
33
+ steps.forEach(function ( s ) {
34
+ var p = prev[s.id];
35
+ var status = s.status === 'done' ? 'done' : 'open';
36
+ if ( !p ) {
37
+ ops.push({ op: 'create', id: s.id, subject: s.title, blockedBy: (s.needs || []).filter(function ( n ) { return open[n]; }) });
38
+ if ( s.status === 'done' ) ops.push({ op: 'complete', id: s.id });
39
+ } else {
40
+ if ( p.subject !== s.title ) ops.push({ op: 'update', id: s.id, subject: s.title });
41
+ if ( p.status !== 'done' && s.status === 'done' ) ops.push({ op: 'complete', id: s.id });
42
+ if ( p.status === 'done' && s.status !== 'done' ) ops.push({ op: 'reopen', id: s.id, reason: s.reason || 'premise drifted (JTMS retraction)' });
43
+ }
44
+ tasks[s.id] = { subject: s.title, status: status };
45
+ });
46
+ // steps gone from the plan stay in the mirror untouched (no phantom deletes pushed at the host).
47
+ Object.keys(prev).forEach(function ( id ) { if ( !tasks[id] ) tasks[id] = prev[id]; });
48
+ return { ops: ops, mirror: { tasks: tasks } };
49
+ }
50
+
51
+ module.exports = { diffPlanToTaskOps: diffPlanToTaskOps };