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,272 @@
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
+ * C6 — the LOCAL-FIRST PROXY CACHE / DISTILLER (the owner's main use case, 2026-07-05). A cache-distiller in
10
+ * front of a FRONTIER model: a query is served from a minimal LOCAL operational stock when it is COVERED, and
11
+ * escalated to the frontier when it is not — enriching the stock in passing. Anti-drift + hallucination-null:
12
+ * • the local side NEVER fabricates an answer from its own weights — it serves only VERIFIED stock (distilled
13
+ * from the frontier = trusted) or ESCALATES → 0 hallucination;
14
+ * • a miss escalates to the frontier (no false negative — the user always gets an answer);
15
+ * • an optional local COVERAGE-CONFIRM (opts.coverageCheck) lets the local model reject a WRONG hit before
16
+ * it is served (a stale/ill-fitting cache entry → invalidate + escalate, never serve a wrong answer);
17
+ * • anti-drift: `drift(query)` invalidates the stock entry so the next ask re-escalates;
18
+ * • `.sgc` ON DEMAND: `pack()`/`load()` ship the minimal operational stock; the store is a bounded local file.
19
+ *
20
+ * THIN assembly over `createLearningLibrary` (the doctrine — no new logic): the always-on cost ladder already
21
+ * IS "MATCH/RETRIEVE (local, 0 cost) → FORGE/ESCALATE (the expensive path)". Here the expensive path is the
22
+ * FRONTIER model, and the cached result is the distilled answer — so `solve` = local-first-then-escalate.
23
+ *
24
+ * @param opts.frontierAsk REQUIRED async (query, ctx) => answer (string | {answer,…}) — the ground-truth escalation.
25
+ * @param opts.keyOf (query) => string cache key (default: the string query, or `digest` of an object).
26
+ * @param opts.signature (query) => {structure,content} — the coverage class (default: EXACT-key = a sound
27
+ * cache). A TYPED signature makes one frontier answer cover a CLASS (opt-in distillation).
28
+ * @param opts.coverageCheck optional async (query, cachedAnswer) => bool — the local model confirms a hit fits;
29
+ * a `false` invalidates the entry and escalates (the "does my stock answer this?" gate).
30
+ * @param opts.verify optional async (query, frontierAnswer) => bool — gate the DISTILLATION: a `false`
31
+ * returns the frontier answer to the user but does NOT pollute the stock (no false neg).
32
+ * @param opts.store a file PATH (durable, cross-restart) or a Map-like (default in-memory, minimal/local).
33
+ * @param opts.* the §4 product knobs via resolveComboDefaults (fail-closed, durable memo, …).
34
+ * @returns {{ answer, drift, pack, load, stats, library }}
35
+ */
36
+
37
+ var learning = require('./learning-library.js');
38
+ var canon = require('../providers/canonicalize.js');
39
+ var llm = require('../providers/llm.js');
40
+ var storeMod = require('../authoring/store.js');
41
+ var retention = require('../authoring/retention.js');
42
+
43
+ function createProxyCache( opts ) {
44
+ opts = opts || {};
45
+ if ( typeof opts.frontierAsk !== 'function' ) throw new Error('createProxyCache needs opts.frontierAsk (async (query) -> answer) — the ground-truth escalation');
46
+ var keyOf = opts.keyOf || function ( q ) { return typeof q === 'string' ? q : canon.digest(q); };
47
+ var normAnswer = function ( r ) { return (r && typeof r === 'object' && r.answer !== undefined) ? r.answer : r; };
48
+
49
+ // SEMANTIC COVERAGE (opt-in): the local model maps a query → a stable canonical KEY, so PARAPHRASES snap to
50
+ // the same key and HIT the stock (a coverage far above exact-repeat). `signature` is sync (the master loop
51
+ // calls it inline), so the async key is computed in answer() and carried on a wrapped problem {__q,__k}.
52
+ var semanticKey = (typeof opts.semanticKey === 'function') ? opts.semanticKey : null;
53
+ var wrapped = function ( p ) { return !!(p && typeof p === 'object' && p.__proxy === true); };
54
+ var qOf = function ( p ) { return wrapped(p) ? p.__q : p; }; // the original query (→ the frontier)
55
+ var kOf = function ( p ) { return wrapped(p) ? p.__k : keyOf(p); }; // the dispatch key (exact or semantic)
56
+ var signature = opts.signature || function ( p ) { return { structure: { q: kOf(p) }, content: {} }; }; // exact/semantic-key = a sound cache
57
+
58
+ // the FRONTIER forge — the ONLY generator (the local model never fabricates an answer). On a verify reject
59
+ // it THROWS carrying the frontier answer, so answer() returns it to the user UNCACHED (no false neg, no
60
+ // polluted stock) — the master loop would otherwise cache a forge result unconditionally.
61
+ var forge = async function ( problem, ctx ) {
62
+ var ans = await opts.frontierAsk(qOf(problem), ctx);
63
+ if ( opts.verify ) {
64
+ var ok = await opts.verify(qOf(problem), normAnswer(ans));
65
+ if ( !ok ) { var e = new Error('distillation rejected by verify'); e.outcome = 'reject'; e.frontierAnswer = normAnswer(ans); throw e; }
66
+ }
67
+ return { result: (ans && typeof ans === 'object' && ans.answer !== undefined) ? ans : { answer: ans }, cost: 1 };
68
+ };
69
+
70
+ // the local operational stock — optionally a self-managing RETENTION store (usage-tracked + auto-evicting):
71
+ // the owner's compass "supprime ce qui n'est jamais utilisé" + a MINIMAL bounded stock. Opt-in via
72
+ // maxStock/evictGrace; absent ⇒ a plain store (measures nothing, evicts nothing).
73
+ var inner = (typeof opts.store === 'string') ? storeMod.createFileStore(opts.store) : (opts.store || new Map());
74
+ var retentionOn = opts.maxStock != null || opts.evictGrace != null || opts.retention === true;
75
+ var stock = retentionOn ? retention.createRetentionStore(inner, { maxStock: opts.maxStock, evictGrace: opts.evictGrace }) : inner;
76
+
77
+ var lib = learning.createLearningLibrary(Object.assign({}, opts, { forge: forge, signature: signature, store: stock }));
78
+ var isLocal = function ( arm ) { return arm === 'match' || arm === 'recall-full' || arm === 'recall-partial'; };
79
+
80
+ return {
81
+ library: lib,
82
+
83
+ /** answer a query — served LOCAL when covered (0 frontier call), else ESCALATED to the frontier and the
84
+ * stock enriched in passing. Never fabricates locally (0 hallucination); a miss escalates (no false neg). */
85
+ answer: async function ( query ) {
86
+ // SEMANTIC key: the local model canonicalizes the query → paraphrases snap to one key (a cheap local
87
+ // call to save an expensive frontier call). Carried on a wrapped problem so the frontier still sees
88
+ // the ORIGINAL query. A key failure degrades safely to exact-key (never blocks the answer).
89
+ var problem = query;
90
+ if ( semanticKey ) {
91
+ try { var k = await semanticKey(query); if ( k != null && String(k).length ) problem = { __proxy: true, __q: query, __k: String(k) }; }
92
+ catch ( e ) { problem = query; }
93
+ }
94
+ var r;
95
+ try { r = await lib.solve(problem); }
96
+ catch ( e ) {
97
+ if ( e && e.outcome === 'reject' ) return { answer: e.frontierAnswer, source: 'frontier', enriched: false, cached: false, arm: 'escalate', cost: e.calls != null ? e.calls : 1 };
98
+ throw e;
99
+ }
100
+ // optional local coverage-confirm: a wrong/stale hit is invalidated + escalated (never served wrong).
101
+ if ( isLocal(r.arm) && typeof opts.coverageCheck === 'function' ) {
102
+ var fits = await opts.coverageCheck(qOf(problem), normAnswer(r.result));
103
+ if ( !fits ) {
104
+ lib.drift(problem);
105
+ try { r = await lib.solve(problem); }
106
+ catch ( e ) { if ( e && e.outcome === 'reject' ) return { answer: e.frontierAnswer, source: 'frontier', enriched: false, cached: false, arm: 'escalate', cost: 1 }; throw e; }
107
+ }
108
+ }
109
+ var local = isLocal(r.arm);
110
+ return { answer: normAnswer(r.result), source: local ? 'local' : 'frontier', enriched: !local, cached: local, arm: r.arm, cost: r.cost };
111
+ },
112
+
113
+ /** a fact drifted → invalidate the stock entry (anti-drift): the next ask re-escalates. Async because a
114
+ * semantic key must be recomputed to hit the same entry. */
115
+ drift: async function ( query ) {
116
+ if ( semanticKey ) { try { var k = await semanticKey(query); if ( k != null && String(k).length ) return lib.drift({ __proxy: true, __q: query, __k: String(k) }); } catch ( e ) {} }
117
+ return lib.drift(query);
118
+ },
119
+
120
+ /** ship / reload the minimal operational stock as a portable `.sgc` (on demand). */
121
+ pack: function ( o ) { return lib.pack(o); },
122
+ load: function ( b, o ) { return lib.load(b, o); },
123
+
124
+ /** the ladder counters {match, recallFull, recallPartial, forge, escalate, cost, calls}. */
125
+ stats: function () { return lib.stats(); },
126
+
127
+ /** THE COMPASS — convergence + lifecycle readout. Ladder coverage (queries served local vs escalated)
128
+ * + the RETENTION stock (size, reuse rate, dead weight, eviction tally). Drives "does it stabilize, at
129
+ * what level, and does it drop what's never used". */
130
+ metrics: function () {
131
+ var s = lib.stats() || {};
132
+ var local = (s.match || 0) + (s.recallFull || 0) + (s.recallPartial || 0);
133
+ var frontier = (s.forge || 0) + (s.escalate || 0);
134
+ var served = local + frontier;
135
+ var u = (stock && typeof stock.usage === 'function') ? stock.usage() : null;
136
+ return {
137
+ served: served, local: local, frontier: frontier,
138
+ coverage: served ? local / served : 0, // cache coverage = queries served locally
139
+ stock: u ? { size: u.size, reused: u.reused, deadWeight: u.deadWeight, reuseRate: u.reuseRate, evicted: u.evicted } : { size: (stock && stock.size) || null }
140
+ };
141
+ },
142
+
143
+ /** run stock eviction on demand → { evicted:[keys] } (no-op without a retention store). */
144
+ evict: function () { return (stock && typeof stock.evict === 'function') ? stock.evict() : { evicted: [] }; },
145
+ /** the retention usage readout, or null (no retention store). */
146
+ usage: function () { return (stock && typeof stock.usage === 'function') ? stock.usage() : null; }
147
+ };
148
+ }
149
+
150
+ /**
151
+ * The FRONTIER adapter (à nu). `createProxyCache` wants `frontierAsk(query, ctx) -> answer`; a chat backend
152
+ * (`makeLocalAsk`, an HTTP endpoint, an API SDK) speaks `({system,user,maxTokens,temperature}) -> text`. This
153
+ * turns the latter into the former with a neutral answer-directly system prompt — symmetric to
154
+ * `makeLocalCoverage`, so a host wires real models with two one-liners:
155
+ * const frontierAsk = makeFrontierAsk(makeLocalAsk({ modelPath: BIG_GGUF, reasoningBudget: 0 }));
156
+ * const { semanticKey, coverageCheck } = makeLocalCoverage({ localAsk: makeLocalAsk({ modelPath: SMALL_GGUF }) });
157
+ * const px = createProxyCache({ frontierAsk, semanticKey, coverageCheck, store: './.sg-proxy.json' });
158
+ * @param ask async ({system,user,maxTokens,temperature}) => text — the chat backend (the ground truth).
159
+ * @param opts.system override the answer-directly system prompt (a domain host tightens it).
160
+ * @param opts.maxTokens answer budget (default 512).
161
+ */
162
+ function makeFrontierAsk( ask, opts ) {
163
+ opts = opts || {};
164
+ if ( typeof ask !== 'function' ) throw new Error('makeFrontierAsk needs a chat ask (async ({system,user}) -> text)');
165
+ var system = opts.system || 'You are a precise, factual assistant. Answer the question directly and correctly. If you are not sure, say so.';
166
+ var maxTokens = opts.maxTokens || 512;
167
+ return async function frontierAsk( query /*, ctx */ ) {
168
+ return await ask({ system: system, user: String(query), maxTokens: maxTokens, temperature: opts.temperature || 0 });
169
+ };
170
+ }
171
+
172
+ /**
173
+ * A ready-made SEMANTIC coverage pair from a local model: `semanticKey` (canonicalize a query → a short stable
174
+ * normal form so paraphrases collide onto ONE cache key) + `coverageCheck` (confirm a stocked answer fits the
175
+ * query). Both are CHEAP local calls that save an expensive frontier call — the local model's "do I cover
176
+ * this?" judge. Pass the pair into createProxyCache.
177
+ *
178
+ * The default `semanticKey` prompt is the KEYWORD-SLOT form (subject + attribute, question-words dropped) — a
179
+ * LIVE probe (2026-07-05, Qwen3-8B-Q4) measured it at 4/6 paraphrase collisions vs 0/6 for a loose
180
+ * "canonical normal form" prompt: a small model keeps a variable amount of the question stem, so the strict
181
+ * slot form is what forces string-identity. The residual misses are SYNONYMS (point↔temperature, ww2↔WWII) —
182
+ * a lattice/synonym-ring concern, not canonicalization. A domain host tightens further via `opts.keyPrompt`.
183
+ * @param opts.localAsk async ({system,user,maxTokens}) => text — the small local model.
184
+ * @param opts.keyPrompt override the canonicalization system prompt (default: the keyword-slot form).
185
+ * @param opts.fitPrompt override the coverage-confirm system prompt (default: a strict yes/no fit check).
186
+ */
187
+ function makeLocalCoverage( opts ) {
188
+ opts = opts || {};
189
+ var ask = opts.localAsk;
190
+ if ( typeof ask !== 'function' ) throw new Error('makeLocalCoverage needs opts.localAsk (async ({system,user}) -> text)');
191
+ var norm = function ( s ) { return String(s).trim().toLowerCase().replace(/[^a-z0-9 ]+/g, '').replace(/\s+/g, ' ').trim(); };
192
+ var keyPrompt = opts.keyPrompt || 'Reduce the question to its ESSENTIAL keywords ONLY: the main subject then the attribute asked about, as 2 to 4 lowercase words. DROP all question words (what/who/when/how/which/is/are/does) and articles. Examples: "What is the capital of France?" -> "france capital". "Who wrote Hamlet?" -> "hamlet author". "How tall is K2?" -> "k2 height". Reply ONLY the keywords.';
193
+ var fitPrompt = opts.fitPrompt || 'Does the ANSWER correctly and fully answer the QUESTION? Reply ONLY "yes" or "no".';
194
+ return {
195
+ semanticKey: async function ( query ) {
196
+ var t = await ask({ system: keyPrompt, user: String(query), maxTokens: 24 });
197
+ return norm(t) || norm(query);
198
+ },
199
+ coverageCheck: async function ( query, cachedAnswer ) {
200
+ var t = await ask({ system: fitPrompt, user: 'QUESTION: ' + String(query) + '\nANSWER: ' + String(cachedAnswer), maxTokens: 4 });
201
+ return /\byes\b/i.test(String(t));
202
+ }
203
+ };
204
+ }
205
+
206
+ /**
207
+ * A TYPED-INTAKE semantic key (the à-nu Intake front door, C0, run STANDALONE for the proxy). Where
208
+ * `makeLocalCoverage`'s key is a keyword STRING (loose string-identity — synonyms miss, no soundness gate), this
209
+ * snaps a prose query to a DECLARED VOCABULARY (`facts` = enum/typed schema) via the SAME canonicalization barrier
210
+ * as the Intake concept (`canonicalize.canonFacts` + `digest`), so a query COVERS a typed CLASS: two paraphrases
211
+ * with the same declared facts collapse to ONE key (a coverage far above keyword strings), and one frontier answer
212
+ * amortizes over the whole class — the "distillation par-classe sur un domaine déclaré" of the roadmap.
213
+ *
214
+ * THE SOUNDNESS BOUNDARY (Intake Invariant 2, carried into the proxy): a REUSABLE key is minted ONLY for a faithful
215
+ * (`typed`) projection. A query whose REQUIRED facts came back OUT-OF-VOCABULARY is `untyped` → the key is `null`,
216
+ * and `createProxyCache` degrades that to EXACT-key (never collapses an OOV query onto a wrong class → it escalates
217
+ * to the frontier). A `partial` (optional-only miss) likewise mints no reusable key. So the typed class NEVER
218
+ * false-admits a prose value as typed — the same non-negotiable as the Intake concept.
219
+ *
220
+ * THE CAVEAT (make it explicit): the typed class is only as sound as the `facts` schema is ANSWER-COMPLETE. A schema
221
+ * that omits an answer-determining dimension collapses two different-answer queries onto one key → a wrong cached
222
+ * hit. PAIR this with `makeLocalCoverage().coverageCheck` (the proxy's `opts.coverageCheck`) — the local model
223
+ * confirms a hit fits before it is served, invalidating + escalating a coarse-schema collision. The schema
224
+ * granularity IS the coverage/soundness knob; coverageCheck is its backstop.
225
+ *
226
+ * @param opts.localAsk async ({system,user,maxTokens}) => text — the small local model (the à-nu intake ask).
227
+ * @param opts.facts the DECLARED facts schema { <key>: {enum:[...]} | {type|grain|...} } (canonicalize spec).
228
+ * @param opts.required the decision-bearing subset (default: ALL fact keys) — a miss here → `untyped` → null key.
229
+ * @param opts.keyPrompt override the extraction system prompt (default: emit the declared facts as JSON).
230
+ * @param opts.maxTokens extraction budget (default 64).
231
+ * @param opts.classOf optional (typedFacts) => string|null — mint the CLASS key from the canonical facts instead
232
+ * of the opaque digest (e.g. `f => f.agg + '|' + f.conds` = a dataset-adapter class name), so
233
+ * the proxy's coverage classes ALIGN with a distilled `.sgc` grammar's class space and a
234
+ * loaded class METHOD is hit by prose queries (distillation par-classe, not just the key).
235
+ * Only consulted on a `typed` projection (the Invariant-2 gate runs first); a falsy return
236
+ * degrades to no key (exact-key → escalate), never a wrong class.
237
+ * @returns { semanticKey, project } — `semanticKey(query)` → the typed digest | null; `project(query)` → the full
238
+ * { status, facts, misses, key } (for a host that wants the status/refusal, not just the key).
239
+ */
240
+ function makeTypedIntakeKey( opts ) {
241
+ opts = opts || {};
242
+ var ask = opts.localAsk;
243
+ if ( typeof ask !== 'function' ) throw new Error('makeTypedIntakeKey needs opts.localAsk (async ({system,user}) -> text)');
244
+ if ( !opts.facts || typeof opts.facts !== 'object' ) throw new Error('makeTypedIntakeKey needs opts.facts (a declared facts schema)');
245
+ var facts = opts.facts, keys = Object.keys(facts);
246
+ var required = opts.required || keys;
247
+ var parseJSON = opts.parseJSON || llm.parseJSON;
248
+ var maxTokens = opts.maxTokens || 64;
249
+ var keyPrompt = opts.keyPrompt || ('Extract these fields from the question as a JSON object with EXACTLY these keys: '
250
+ + keys.join(', ') + '. Use the question\'s own words for each value; if a field is not present, omit it. Reply ONLY the JSON.');
251
+
252
+ // the à-nu intake projection: prose → the declared typed facts → status → a reusable digest ONLY when typed.
253
+ var project = async function ( query ) {
254
+ var raw;
255
+ try { raw = parseJSON(await ask({ system: keyPrompt, user: String(query), maxTokens: maxTokens })); }
256
+ catch ( e ) { raw = {}; }
257
+ var cf = canon.canonFacts(raw || {}, facts);
258
+ var reqMiss = required.filter(function ( k ) { return cf.misses.indexOf(k) !== -1; });
259
+ var status = (reqMiss.length ? 'untyped' : (cf.misses.length ? 'partial' : 'typed'));
260
+ var key = null;
261
+ if ( status === 'typed' ) key = opts.classOf ? (opts.classOf(cf.facts) || null) : canon.digest(cf.facts);
262
+ return { status: status, facts: cf.facts, misses: cf.misses, missing: reqMiss, key: key };
263
+ };
264
+ return {
265
+ project: project,
266
+ // the semanticKey seam of createProxyCache: a typed digest collapses the class; null → exact-key (the OOV
267
+ // boundary — an untyped query never collapses onto a wrong class, it escalates).
268
+ semanticKey: async function ( query ) { return (await project(query)).key; }
269
+ };
270
+ }
271
+
272
+ module.exports = { createProxyCache: createProxyCache, makeLocalCoverage: makeLocalCoverage, makeFrontierAsk: makeFrontierAsk, makeTypedIntakeKey: makeTypedIntakeKey };
@@ -0,0 +1,94 @@
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
+ * C5 — SUPERVISED SELF-MODIFICATION (roadmap P3-bis). OPT-IN and GUARDED. A thin, prudent packaging of
21
+ * the engine's live-rule-editing bricks: `authorConcept` (CEGIS — propose→validate→install→test→refine),
22
+ * the reactive supervisor (`supervise`: Stuck→hypothesis→eval→revert), `patchConcept`/`addConcept`
23
+ * (hot-patch a live expert + re-evaluate), and `relearn` (the autonomous un-learn loop: blame→revise→
24
+ * patch, as reactive concepts).
25
+ *
26
+ * It edits the LIVE rules, so it is exposed with PRUDENCE:
27
+ * • `author()` REQUIRES an explicit proposer (the "judge" — an `LLM::complete` in production, a
28
+ * deterministic stub in tests). You cannot author without declaring how proposals are made/judged.
29
+ * • REVERSIBILITY is THE guarantee: every structural op is a captured revision, and `rollbackTo(rev)`
30
+ * ("git for reasoning") restores any prior coherent state. Two author-time + behavioral oracles
31
+ * bound the CEGIS search (the validator rejects a malformed candidate before it touches the graph;
32
+ * the live graph rejects an unmet goal).
33
+ *
34
+ * NOT activated by default anywhere — a host builds this combo explicitly. The bricks stay usable "à nu"
35
+ * (`Graph.authoring.author/supervise/relearn` + the engine MOE API `patchConcept/addConcept/rollbackTo`).
36
+ *
37
+ * @param opts.graph the live graph to modify (REQUIRED).
38
+ * @param opts.propose async ({goal, history, round}) => proposal the AI author/judge (required for author()).
39
+ * @param opts.validate author-time validator config (palette/knownFacts/collectionKeys/strict) for the CEGIS oracle.
40
+ * @returns {{ graph, author, supervise, patch, addConcept, rollbackTo, revisions, relearn }}
41
+ */
42
+
43
+ var author = require('../authoring/author.js');
44
+ var supervise = require('../authoring/supervise.js');
45
+ var relearn = require('../authoring/relearn.js');
46
+
47
+ function createSelfMod( opts ) {
48
+ opts = opts || {};
49
+ var graph = opts.graph;
50
+ if ( !graph || typeof graph.patchConcept !== 'function' )
51
+ throw new Error('createSelfMod needs opts.graph (a live Graph with the MOE API)');
52
+
53
+ // a structural op → a promise. Mirrors author.js#applyOp: a NO-OP op destabilizes nothing, so the
54
+ // "stabilize" callback never fires — but the graph is already quiescent, so resolve on the next tick.
55
+ function structuralOp( invoke ) {
56
+ return new Promise(function ( resolve, reject ) {
57
+ var done = false, finish = function () { if ( !done ) { done = true; resolve(); } };
58
+ try { invoke(finish); } catch ( e ) { return reject(e); }
59
+ if ( !graph._unstable.length && !graph._triggeredCastCount ) setTimeout(finish);
60
+ });
61
+ }
62
+
63
+ return {
64
+ graph: graph,
65
+
66
+ /** CEGIS-author a concept toward `spec.goal` (propose→validate→install→test→refine). Requires a
67
+ * proposer (opts.propose or spec.propose — the guard). → { ok, concept?, rounds, reason? }. */
68
+ author: function ( spec ) {
69
+ spec = spec || {};
70
+ var propose = spec.propose || opts.propose;
71
+ if ( typeof propose !== 'function' )
72
+ throw new Error('self-mod author() needs a proposer: opts.propose or spec.propose (the AI author/judge)');
73
+ return author.authorConcept(graph, Object.assign({ validate: opts.validate }, spec, { propose: propose }));
74
+ },
75
+
76
+ /** run the reactive supervisor (Stuck→hypothesis→eval→revert) toward `spec`. */
77
+ supervise: function ( spec ) { return supervise.supervise(graph, spec || {}); },
78
+
79
+ /** hot-patch a live expert + re-evaluate (the change is a captured revision). */
80
+ patch: function ( nameOrId, updates ) { return structuralOp(function ( cb ) { graph.patchConcept(nameOrId, updates, cb); }); },
81
+ /** add a live concept under `parent` (null = root) + stabilize (a revision). */
82
+ addConcept: function ( parent, schema ) { return structuralOp(function ( cb ) { graph.addConcept(parent == null ? null : parent, schema, cb); }); },
83
+
84
+ /** THE reversibility guarantee: restore a prior coherent revision (git-for-reasoning). */
85
+ rollbackTo: function ( rev ) { return graph.rollbackTo(rev); },
86
+ /** the revisions available to rollbackTo (ascending). */
87
+ revisions: function () { return graph.getRevisions(); },
88
+
89
+ /** the autonomous un-learn loop as reactive concepts (blame→revise→patch) — host wires tree+providers. */
90
+ relearn: { tree: relearn.relearnTree, providers: relearn.makeRelearnProviders }
91
+ };
92
+ }
93
+
94
+ module.exports = { createSelfMod: createSelfMod };
@@ -0,0 +1,102 @@
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
+ * audit — C-audit: the durable executor's INSPECTION surface (design doc §8, "a headline win with no owner").
10
+ * The conception sells AUDITABILITY (typed/defeasible/auditable/versioned belief — the moat over opaque CBR/RAG);
11
+ * this makes it concrete. Pure + READ-ONLY over the `CheckpointStore` marking — no belief, no mutation.
12
+ *
13
+ * The marking already records everything needed: each token's `id`, `recordId`, `placeId`, `status`, `parentId`
14
+ * (the fan-out/fan-in lineage), `reason` (the blame), `payload` (the result). `auditRun` reconstructs:
15
+ * - the DERIVATION FOREST per record: the parentId tree (root case → map children → fold collector);
16
+ * - a per-record VERDICT: done / failed / pending, the terminal place, the result payload, and — on failure —
17
+ * the BLAME reason (e.g. `contract:post-violated:…`, `group-failed`, `task-error:…`);
18
+ * - whether the record FANNED OUT (a map) and was FOLDED back (a collector reached a sink);
19
+ * - run TOTALS by status + the blame list.
20
+ *
21
+ * This is the audit trail no surface-similarity store can give: WHY a derivation holds or was retracted, traceable
22
+ * to the typed premise and the exact step that failed.
23
+ */
24
+
25
+ const TERMINAL = { done: 1, failed: 1 };
26
+
27
+ /**
28
+ * Build the audit trace for a run. Read-only.
29
+ * @param store a CheckpointStore
30
+ * @param runId
31
+ * @param opts.sinks the net's sink places (default ['done']) — to label a terminal token done-vs-elsewhere
32
+ * @returns { records: { <recordId>: {status, terminal, result, blame, fannedOut, folded, lineage:[…]} }, totals }
33
+ */
34
+ function auditRun( store, runId, opts ) {
35
+ opts = opts || {};
36
+ const sinks = new Set(opts.sinks || ['done']);
37
+ const marking = store.marking(runId) || {};
38
+ const toks = [];
39
+ for ( const place of Object.keys(marking) ) for ( const t of marking[place] ) toks.push(t);
40
+
41
+ const byRecord = {};
42
+ for ( const t of toks ) (byRecord[t.recordId] = byRecord[t.recordId] || []).push(t);
43
+
44
+ const records = {};
45
+ const totals = { records: 0, done: 0, failed: 0, pending: 0, tokens: toks.length, blames: [] };
46
+
47
+ for ( const rid of Object.keys(byRecord) ) {
48
+ const ts = byRecord[rid].slice().sort(( a, b ) => idNum(a.id) - idNum(b.id));
49
+ const failed = ts.filter(( t ) => t.status === 'failed' );
50
+ const doneToks = ts.filter(( t ) => t.status === 'done' );
51
+ const fannedOut = ts.some(( t ) => t.status === 'consumed' ); // a fan-out consumes its source
52
+ const folded = ts.some(( t ) => t.parentId != null && t.status === 'done' && hasJoinedSiblings(ts, t.parentId) );
53
+
54
+ // verdict: a SURVIVORS fold wins FIRST — a collector reached a sink carrying `_partial` despite some FAILED
55
+ // (dropped) shards: that is done(partial), NOT a record failure (the drops are expected, surfaced not fatal).
56
+ // Else failed wins (any failure quarantines the record — fail-fast); else done iff a token reached a sink;
57
+ // else still pending (claimable/leased/parked).
58
+ const partialDone = doneToks.find(( t ) => t.payload && t.payload._partial === true );
59
+ let status, terminal = null, result, blame = null, partial = false, dropped = 0;
60
+ if ( partialDone ) {
61
+ status = 'done'; partial = true;
62
+ terminal = partialDone.placeId; result = partialDone.payload;
63
+ dropped = partialDone.payload._dropped != null ? partialDone.payload._dropped : failed.length;
64
+ } else if ( failed.length ) {
65
+ status = 'failed';
66
+ blame = failed.map(( t ) => t.reason ).find(( r ) => r && !/^sibling-failed$|^group-failed$/.test(r) ) || failed[0].reason;
67
+ terminal = failed[0].placeId;
68
+ } else if ( doneToks.length ) {
69
+ status = 'done';
70
+ const last = doneToks[doneToks.length - 1]; // the fold collector, if any, is the last-created done
71
+ terminal = last.placeId; result = last.payload;
72
+ } else {
73
+ status = 'pending';
74
+ }
75
+
76
+ records[rid] = {
77
+ status, partial, dropped, terminal, result, blame, fannedOut, folded,
78
+ tokens: ts.length,
79
+ lineage: ts.map(( t ) => ({ id: t.id, place: t.placeId, status: t.status, parentId: t.parentId == null ? null : t.parentId,
80
+ reason: t.reason == null ? undefined : t.reason })),
81
+ };
82
+ totals.records++; totals[status]++;
83
+ if ( blame ) totals.blames.push({ recordId: rid, reason: blame });
84
+ }
85
+ return { records, totals };
86
+ }
87
+
88
+ function idNum( id ) { const n = parseInt(String(id).replace(/^t/, ''), 10); return isNaN(n) ? 0 : n; }
89
+ function hasJoinedSiblings( ts, parentId ) { return ts.some(( t ) => t.parentId === parentId && t.status === 'joined' ); }
90
+
91
+ /** A compact one-line-per-record summary (for a CLI / log). */
92
+ function auditSummary( audit ) {
93
+ const lines = [];
94
+ for ( const rid of Object.keys(audit.records) ) {
95
+ const r = audit.records[rid];
96
+ lines.push(`${rid}: ${r.status}${r.partial ? ' (partial, ' + r.dropped + ' dropped)' : ''}${r.fannedOut ? ' (map' + (r.folded ? '→fold' : '') + ')' : ''}` +
97
+ `${r.blame ? ' — ' + r.blame : ''} @${r.terminal || '?'}`);
98
+ }
99
+ return lines.join('\n');
100
+ }
101
+
102
+ module.exports = { auditRun, auditSummary };