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,177 @@
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
+ * glossary — the P2 cross-round terminology REFERENCE (the "glossaire de campagne"): a persistent,
10
+ * lattice-backed store of canonical GROUNDED notions (theses / arguments / tags) that follows the pool
11
+ * BETWEEN refinement rounds. It is the condition of a reliable re-split (the C9 re-plan / the generative
12
+ * loop's round 2): without a managed reference, duplicates go invisible, meaning drifts, and a re-split
13
+ * cites phantom keys. It is [ZERO-CORE] — it ASSEMBLES organs that already exist and are tested:
14
+ *
15
+ * • RECONCILIATION composes TWO gates (they compose, they do not compete):
16
+ * – witness-overlap ≥ θ = the GROUNDED semantic selector — which existing notion a new term duplicates
17
+ * (it picks the ring MEMBER). Same denominator as the generative loop's merge check (critique.js).
18
+ * – `registry.js#mergeRingProposals` = the SAFETY + AUDIT layer — it admits the alias under CONFLUENCE
19
+ * (a surface phrasing can never denote TWO notions → no silent collision), tags provenance, and BUMPS
20
+ * the version (the invalidation signal that version-gates stale caches/digests). A bad merge is
21
+ * RETRACTABLE (`retractRingAlias`) — a plain Set (the harness) can do none of this.
22
+ * • ENTRY-level JTMS retract (a notion whose witnesses left the pool) cascades via the uniform in-pool test
23
+ * — the same structural reconcile the C9/boucle-generative harness proved, here over the PERSISTENT store.
24
+ *
25
+ * Modelled as a registry with ONE Tier-2 key `notion` whose `enum` GROWS as notions are harvested (the
26
+ * canonical ids) and whose `synonyms[id]` holds the surface phrasings; the ring primitives operate
27
+ * generically on `reg.keys[key]`, so a hand-built single-key registry is a legal registry.
28
+ *
29
+ * const g = createGlossary();
30
+ * g.harvest({ text: 'open-source maximizes adoption', witnesses: ['p1','p3'], side: 'PRO' }); // → {status:'added', id:'n1'}
31
+ * g.harvest({ text: 'openness drives uptake', witnesses: ['p1','p4'], side: 'PRO' }); // → {status:'merged', id:'n1'} (overlap 0.5, alias audited)
32
+ * g.inject(); // citable vocabulary block
33
+ * g.reconcile(pool); // JTMS entry-retract (witnesses gone → cascade)
34
+ *
35
+ * NOTE: a notion enters the ring only when GROUNDED (≥1 witness) — 0-fabrication: harvest never invents a
36
+ * witness, inject renders only the store. An ungrounded (open) point is not a canonical notion yet.
37
+ */
38
+ const { digest, normToken } = require('../providers/canonicalize.js');
39
+ const { mergeRingProposals, retractRingAlias, creditRingAlias } = require('./registry.js');
40
+ const { clusterByGrounding, arbitrate } = require('./granularity.js');
41
+
42
+ const overlapOf = ( a, b ) => { // |shared| / |candidate|, candidate = a
43
+ if ( !a.length ) return 0;
44
+ const set = new Set(b);
45
+ return a.filter(( x ) => set.has(x) ).length / a.length;
46
+ };
47
+
48
+ function createGlossary( opts ) {
49
+ opts = opts || {};
50
+ const KEY = opts.key || 'notion';
51
+ const THETA = opts.overlap != null ? opts.overlap : 0.5;
52
+ // a legal single-key registry (Tier-2, open interior); the ring primitives read `reg.keys[key]` generically.
53
+ let registry = { version: 'v1', frozen: false, conflicts: [], ringProvenance: {},
54
+ keys: { [KEY]: { tier: 2, enum: [], synonyms: {}, producers: [], consumers: [], values: [] } } };
55
+ const entries = new Map(); // id -> { id, text, witnesses[], side, type, round, provenance, status, aliases[] }
56
+ let counter = 0;
57
+
58
+ const memberOfAlias = ( text ) => { // the notion a surface phrasing already denotes (or null)
59
+ const na = normToken(text), syn = registry.keys[KEY].synonyms || {};
60
+ for ( const m of Object.keys(syn) ) if ( (syn[m] || []).some(( a ) => normToken(a) === na ) ) return m;
61
+ return null;
62
+ };
63
+ const propose = ( member, alias, via ) => { // admit an alias through the CONFLUENCE gate; swap in the grown (versioned) registry on admit
64
+ const r = mergeRingProposals(registry, [{ key: KEY, member, alias, via: via || 'harvest' }]);
65
+ if ( r.admitted.length ) { registry = r.registry; return { admitted: true }; }
66
+ return { admitted: false, reason: (r.rejected[0] && r.rejected[0].reason) || 'rejected' };
67
+ };
68
+
69
+ /**
70
+ * Harvest one grounded element into the glossary. Returns the reconciliation outcome:
71
+ * { status:'added', id } a NEW canonical notion
72
+ * { status:'merged', id, alias } a duplicate of `id` (surface identity OR grounded overlap ≥ θ) — alias audited
73
+ * { status:'conflict', id, rejectedReason } the surface would denote TWO notions — SURFACED, never silently merged
74
+ * { status:'ungrounded' } no witness → not a canonical notion (0-fabrication)
75
+ * @param entry { text, witnesses:[argId], side?, type?, round?, provenance? }
76
+ */
77
+ function harvest( entry ) {
78
+ const text = String((entry && entry.text) || '').trim();
79
+ const witnesses = ((entry && entry.witnesses) || []).slice();
80
+ if ( !text ) return { status: 'ungrounded', reason: 'no text' };
81
+ if ( !witnesses.length ) return { status: 'ungrounded', reason: 'no witness (0-fabrication: a notion enters the ring only when grounded)' };
82
+ const side = entry.side != null ? String(entry.side).toUpperCase() : null;
83
+
84
+ // (1) SURFACE IDENTITY — the exact phrasing already names a notion → that notion, definitively.
85
+ const surf = memberOfAlias(text);
86
+ if ( surf ) { entries.get(surf).aliases.push({ text, witnesses, via: 'surface' }); return { status: 'merged', id: surf, alias: text }; }
87
+
88
+ // (2) GROUNDED OVERLAP — witness-overlap ≥ θ picks the member; the ring gate admits/audits the alias.
89
+ let best = null, bestOv = 0;
90
+ for ( const e of entries.values() ) {
91
+ if ( e.status !== 'active' || !e.witnesses.length ) continue;
92
+ if ( side && e.side && e.side !== side ) continue; // never merge across sides
93
+ const ov = overlapOf(witnesses, e.witnesses);
94
+ if ( ov > bestOv ) { bestOv = ov; best = e; }
95
+ }
96
+ if ( best && bestOv >= THETA ) {
97
+ const r = propose(best.id, text, entry.provenance);
98
+ if ( r.admitted ) { best.aliases.push({ text, witnesses, via: entry.provenance || 'harvest' }); return { status: 'merged', id: best.id, alias: text }; }
99
+ return { status: 'conflict', id: best.id, rejectedReason: r.reason }; // confluence refused — SURFACED, not silently collided
100
+ }
101
+
102
+ // (3) NEW GROUNDED NOTION — extend the enum, register its text as the first alias (version bumps).
103
+ const id = 'n' + (++counter);
104
+ registry.keys[KEY].enum = registry.keys[KEY].enum.concat([id]);
105
+ const r = propose(id, text, entry.provenance);
106
+ if ( !r.admitted ) { // the text already denotes another notion → a disguised merge into its owner
107
+ registry.keys[KEY].enum = registry.keys[KEY].enum.filter(( x ) => x !== id );
108
+ const owner = memberOfAlias(text);
109
+ if ( owner ) { entries.get(owner).aliases.push({ text, witnesses, via: 'surface-late' }); return { status: 'merged', id: owner, alias: text }; }
110
+ counter--; return { status: 'conflict', id: null, rejectedReason: r.reason };
111
+ }
112
+ entries.set(id, { id, text, witnesses: witnesses.slice(), side, type: entry.type || null,
113
+ round: entry.round != null ? entry.round : 0, provenance: entry.provenance || 'harvest', status: 'active', aliases: [] });
114
+ return { status: 'added', id };
115
+ }
116
+
117
+ /**
118
+ * JTMS entry-retract over the PERSISTENT store — an active notion whose witnesses no longer ALL live in the
119
+ * pool is retracted. The uniform in-pool test gives the CASCADE for free (a shared witness leaving drops
120
+ * every notion citing it; a notion retracted for one bad witness never drags down notions sharing its
121
+ * still-valid witnesses). Pure + structural; identical logic to critique.js#reconcile, over the store.
122
+ * @returns retracted ids
123
+ */
124
+ function reconcile( pool ) {
125
+ const inPool = new Set((pool || []).map(( a ) => typeof a === 'string' ? a : a.id ));
126
+ const retracted = [];
127
+ for ( const e of entries.values() )
128
+ if ( e.status === 'active' && e.witnesses.length && !e.witnesses.every(( w ) => inPool.has(w) ) ) { e.status = 'retracted'; retracted.push(e.id); }
129
+ return retracted;
130
+ }
131
+
132
+ /** Render the canonical notions as a deterministic, sorted CITABLE-VOCABULARY block (the re-découpe injection). */
133
+ function inject( o ) {
134
+ o = o || {};
135
+ const activeOnly = o.activeOnly !== false;
136
+ const list = [...entries.values()].filter(( e ) => (activeOnly ? e.status === 'active' : true) && (!o.side || e.side === o.side) );
137
+ if ( !list.length ) return '';
138
+ return list.map(( e ) => '- ' + e.id + ': ' + e.text + (e.side ? ' [' + e.side + ']' : '') ).join('\n');
139
+ }
140
+ /** The set of citable notion ids — a re-ask that cites a key OUTSIDE this set is a phantom (caught by validation). */
141
+ function citableKeys( o ) {
142
+ o = o || {};
143
+ const activeOnly = o.activeOnly !== false;
144
+ return new Set([...entries.values()].filter(( e ) => (activeOnly ? e.status === 'active' : true)).map(( e ) => e.id ));
145
+ }
146
+
147
+ /** Retract a bad merge (a wrong alias) — the un-learn verb; bumps the version (the invalidation signal). */
148
+ function retractAlias( id, alias ) {
149
+ const r = retractRingAlias(registry, KEY, alias); registry = r.registry;
150
+ const e = entries.get(id); if ( e ) e.aliases = e.aliases.filter(( a ) => normToken(a.text) !== normToken(alias) );
151
+ return { retracted: r.retracted, member: r.member, version: registry.version };
152
+ }
153
+ /** Credit a verified reuse of an alias — support++, NEVER bumps the version (resolution semantics unchanged). */
154
+ function credit( alias ) {
155
+ const r = creditRingAlias(registry, KEY, alias); registry = r.registry;
156
+ return { member: r.member, support: r.support, version: registry.version };
157
+ }
158
+
159
+ // P2 granularity ARBITER over the active notions — group them into grounded dimensions (co-citation of
160
+ // witnesses) and return the lazy-2-régime frame verdict (coherent | mixed=the re-plan TOO-NARROW signal |
161
+ // unstructured=escalate to Q2). Delegates to granularity.js; the glossary just supplies its active notions.
162
+ const activeItems = ( o ) => [...entries.values()].filter(( e ) => e.status === 'active' && e.witnesses.length )
163
+ .map(( e ) => ({ id: e.id, witnesses: e.witnesses, side: e.side }) );
164
+ return {
165
+ harvest, reconcile, inject, citableKeys, retractAlias, credit,
166
+ cluster: ( o ) => clusterByGrounding(activeItems(o), o),
167
+ arbitrate: ( o ) => arbitrate(activeItems(o), o),
168
+ notions: ( o ) => [...entries.values()].filter(( e ) => !(o && o.activeOnly) || e.status === 'active' ),
169
+ get: ( id ) => entries.get(id) || null,
170
+ version: () => registry.version,
171
+ snapshot: () => ({ version: registry.version, notions: [...entries.values()].map(( e ) => ({ ...e, aliases: e.aliases.slice() })),
172
+ rings: JSON.parse(JSON.stringify(registry.keys[KEY].synonyms || {})), provenance: Object.assign({}, registry.ringProvenance) }),
173
+ fingerprint: () => digest({ v: registry.version, n: [...entries.values()].map(( e ) => e.id + ':' + e.status + ':' + e.witnesses.join(',') ).sort() })
174
+ };
175
+ }
176
+
177
+ module.exports = { createGlossary, overlapOf };
@@ -0,0 +1,191 @@
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
+ * Grammar graph (host-side authoring tool, zero core change) — the second, orthogonal view
21
+ * the Studio was missing: not the IS-A tree (`childConcepts`, already shown), but the
22
+ * concept↔fact FLUX graph, where the real interactions live.
23
+ *
24
+ * A grammar's concepts interact through FACTS: a concept WRITES facts (its self-flag + the
25
+ * keys its `applyMutations` template sets) and READS facts (its `require` LHS + the `$ref`s in
26
+ * its `assert`/`ensure`, with the dependency's POLARITY — a `!$f` / `$f==false` read is a
27
+ * defeasance edge). Crossing two concept sets, a fact written in one set and read in another is
28
+ * a CROSS-CORPUS link; a fact written in two sets is a silent COLLISION (the `leadTime` trap);
29
+ * a fact read but produced by nobody is an external ENTRY POINT (seed / engine input).
30
+ *
31
+ * const { conceptFactGraph } = require('./grammar-graph');
32
+ * const g = conceptFactGraph({ common: tree, clinical: tree2 });
33
+ * // g.concepts, g.facts, g.edges, g.crossCorpus, g.collisions, g.entryPoints, g.tiling
34
+ *
35
+ * The ref / polarity / produced-fact extraction reuses validate.js (single source of truth);
36
+ * the tiling overlay (separators / forks / frontier alphabets) comes from decompose.js#forkPlan.
37
+ * Pure static analysis — no engine runtime.
38
+ */
39
+ const { eachConcept, refsOf, refKeyOf, templateKeys, negatedRefKeys } = require('./validate');
40
+ const { parseExpression } = require('../graph/expr');
41
+ const { forkPlan } = require('./decompose');
42
+
43
+ const asArray = (v) => (v == null ? [] : Array.isArray(v) ? v : [v]);
44
+ const schemaOf = (c) => (c && c._schema) || c || {};
45
+
46
+ function conceptKind( schema ) {
47
+ const p = schema.provider;
48
+ if ( !p ) return 'pure';
49
+ const name = Array.isArray(p) ? p[0] : p;
50
+ return (typeof name === 'string' && name.startsWith('LLM::')) ? 'llm' : 'provider';
51
+ }
52
+
53
+ // A `$$key` in a mutation template marks a bagRef (external data) — detect it for the flag badge.
54
+ function hasBagRef( applyMutations ) {
55
+ for ( const obj of asArray(applyMutations) ) {
56
+ if ( !obj || typeof obj !== 'object' ) continue;
57
+ for ( const k of Object.keys(obj) ) if ( k.startsWith('$$') ) return true;
58
+ }
59
+ return false;
60
+ }
61
+
62
+ function conceptFlags( schema ) {
63
+ return {
64
+ onCast : !!schema.onCast, // standing-cluster subscribe hook
65
+ cleaner : !!schema.cleaner, // retraction / teardown hook
66
+ autoCastFalse: schema.autoCast === false,
67
+ enum : schema.type === 'enum',
68
+ bagRef : hasBagRef(schema.applyMutations)
69
+ };
70
+ }
71
+
72
+ // Is this a set-map ({ setName: tree }) or a single bare tree (has its own childConcepts)?
73
+ function asSetMap( conceptMap ) {
74
+ if ( conceptMap && Object.prototype.hasOwnProperty.call(conceptMap, 'childConcepts') )
75
+ return { default: conceptMap };
76
+ return conceptMap || {};
77
+ }
78
+
79
+ // Shallow-merge the top-level childConcepts of every set into one tree (nested childConcepts are
80
+ // preserved) for the tiling overlay. An approximation of the engine's deepmerge — enough for the
81
+ // static decomposition; a same-named top-level concept in two sets is overwritten (last wins).
82
+ function mergeTrees( trees ) {
83
+ const childConcepts = {};
84
+ for ( const t of trees ) Object.assign(childConcepts, (t && t.childConcepts) || {});
85
+ return { childConcepts };
86
+ }
87
+
88
+ /**
89
+ * Build the concept↔fact grammar graph from a concept map (or a single tree).
90
+ * @param conceptMap { setName: tree, … } or a single concept tree
91
+ * @returns {{
92
+ * concepts: Array<{id,name,set,kind,flags}>,
93
+ * facts: Array<{key,producedBy:string[],consumedBy:Array<{name,polarity,via}>,sets:string[]}>,
94
+ * edges: Array<{kind:'writes',concept,fact} | {kind:'reads',concept,fact,polarity,via}>,
95
+ * crossCorpus: Array<{fact,fromSet,toSet}>,
96
+ * collisions: Array<{fact,sets:string[]}>,
97
+ * entryPoints: string[],
98
+ * tiling: object|null
99
+ * }}
100
+ */
101
+ function conceptFactGraph( conceptMap ) {
102
+ const map = asSetMap(conceptMap);
103
+ const sets = Object.keys(map);
104
+
105
+ const concepts = [];
106
+ const setOfConcept = new Map(); // concept name -> set
107
+ const edges = [];
108
+ const producersByFact = new Map(); // fact -> Set(concept name)
109
+ const producerSetsByFact = new Map(); // fact -> Set(set)
110
+
111
+ for ( const set of sets ) {
112
+ eachConcept(map[set], ( c, key ) => {
113
+ const schema = schemaOf(c);
114
+ const name = c._name || key;
115
+ if ( !name ) return; // unnamed node — validate.js flags it; skip here
116
+ concepts.push({ id: c._id || name, name, set, kind: conceptKind(schema), flags: conceptFlags(schema) });
117
+ setOfConcept.set(name, set);
118
+
119
+ // produced: self-flag + applyMutations keys
120
+ const produced = new Set([name, ...templateKeys(schema.applyMutations)]);
121
+ for ( const f of produced ) {
122
+ edges.push({ kind: 'writes', concept: name, fact: f });
123
+ if ( !producersByFact.has(f) ) producersByFact.set(f, new Set());
124
+ producersByFact.get(f).add(name);
125
+ if ( !producerSetsByFact.has(f) ) producerSetsByFact.set(f, new Set());
126
+ producerSetsByFact.get(f).add(set);
127
+ }
128
+
129
+ // consumed: require (positive); assert/ensure (polarity from the AST)
130
+ for ( const r of refsOf(schema.require, false) )
131
+ edges.push({ kind: 'reads', concept: name, fact: refKeyOf(r).key, polarity: '+', via: 'require' });
132
+ for ( const via of ['assert', 'ensure'] )
133
+ for ( const e of asArray(schema[via]) ) {
134
+ if ( typeof e !== 'string' ) continue;
135
+ let ast; try { ast = parseExpression(e); } catch ( _e ) { ast = null; }
136
+ const negs = negatedRefKeys(ast);
137
+ for ( const r of refsOf(e, true) ) {
138
+ const k = refKeyOf(r).key;
139
+ edges.push({ kind: 'reads', concept: name, fact: k, polarity: negs.has(k) ? '-' : '+', via });
140
+ }
141
+ }
142
+ });
143
+ }
144
+
145
+ // consumers per fact
146
+ const consumersByFact = new Map(); // fact -> [{name,polarity,via}]
147
+ const consumerSetsByFact = new Map(); // fact -> Set(set)
148
+ for ( const e of edges ) if ( e.kind === 'reads' ) {
149
+ if ( !consumersByFact.has(e.fact) ) consumersByFact.set(e.fact, []);
150
+ consumersByFact.get(e.fact).push({ name: e.concept, polarity: e.polarity, via: e.via });
151
+ const s = setOfConcept.get(e.concept);
152
+ if ( !consumerSetsByFact.has(e.fact) ) consumerSetsByFact.set(e.fact, new Set());
153
+ if ( s ) consumerSetsByFact.get(e.fact).add(s);
154
+ }
155
+
156
+ const allFacts = new Set([...producersByFact.keys(), ...consumersByFact.keys()]);
157
+ const facts = [...allFacts].sort().map(( key ) => ({
158
+ key,
159
+ producedBy: [...(producersByFact.get(key) || [])].sort(),
160
+ consumedBy: (consumersByFact.get(key) || []).slice()
161
+ .sort(( a, b ) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
162
+ sets: [...new Set([...(producerSetsByFact.get(key) || []), ...(consumerSetsByFact.get(key) || [])])].sort()
163
+ }));
164
+
165
+ // collisions: a fact written by concepts in >= 2 sets
166
+ const collisions = [];
167
+ for ( const [fact, setSet] of producerSetsByFact )
168
+ if ( setSet.size >= 2 ) collisions.push({ fact, sets: [...setSet].sort() });
169
+ collisions.sort(( a, b ) => (a.fact < b.fact ? -1 : 1));
170
+
171
+ // cross-corpus links: produced in set X, consumed in set Y != X
172
+ const crossCorpus = [];
173
+ for ( const fact of allFacts ) {
174
+ const pSets = producerSetsByFact.get(fact) || new Set();
175
+ const cSets = consumerSetsByFact.get(fact) || new Set();
176
+ for ( const from of pSets ) for ( const to of cSets ) if ( from !== to )
177
+ crossCorpus.push({ fact, fromSet: from, toSet: to });
178
+ }
179
+ crossCorpus.sort(( a, b ) => (a.fact < b.fact ? -1 : a.fact > b.fact ? 1 : a.fromSet < b.fromSet ? -1 : 1));
180
+
181
+ // entry points: read by someone, produced by no one (external / seed / engine input)
182
+ const entryPoints = [...allFacts].filter(( f ) => !producersByFact.has(f) && consumersByFact.has(f)).sort();
183
+
184
+ // tiling overlay (best-effort; never throw the whole derivation on a degenerate corpus)
185
+ let tiling = null;
186
+ try { tiling = forkPlan(mergeTrees(sets.map(( s ) => map[s]))); } catch ( _e ) { tiling = null; }
187
+
188
+ return { concepts, facts, edges, crossCorpus, collisions, entryPoints, tiling };
189
+ }
190
+
191
+ module.exports = { conceptFactGraph };
@@ -0,0 +1,80 @@
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
+ * granularity — the STRUCTURAL granularity ARBITER (P2's 4th client, the sonde's grounded dimensions):
10
+ * cluster grounded notions / key-points into candidate DIMENSIONS by their GROUNDING overlap (co-citation
11
+ * of witnesses), with NO prose parsing. The sonde (WIP 2026-07-13) measured that the real dimensions of a
12
+ * question come from CLUSTERING the KPs on their grounding, not from asking Q2 to parse them (its "moral;
13
+ * legal" prior is a shallow cliché). Two items are in the same dimension iff their witness-sets share ≥
14
+ * `minShared` arguments — transitively, via connected components on the co-citation graph.
15
+ *
16
+ * The arbiter's job is to ARBITRATE the lazy 2-régime factorization (sonde): when the grounding is dense
17
+ * enough to form ≥2 grounded groups, it RESOLVES the dimensions for free ('mixed' = the re-plan's
18
+ * `frame: TOO-NARROW` signal, grounded — the moral⊥legal separation, structurally); when the grounding is
19
+ * a single connected block it is a 'coherent' frame (decide normally); when it is all singletons (sparse
20
+ * co-citation — measured ~6% multi-matched on ArgKP) there is no grounded dimension structure and it
21
+ * ESCALATES ('unstructured' → Q2 proposes dimensions, seeded by the groups). It never hallucinates a
22
+ * dimension (the discriminating negative control). Deterministic, grounded, ZERO-CORE.
23
+ *
24
+ * const { arbitrate } = require('skynet-graph/lib/authoring/granularity');
25
+ * arbitrate([{id:'n1',witnesses:['a','b']}, {id:'n2',witnesses:['b','c']}, {id:'n3',witnesses:['x','y']}]);
26
+ * // → { frame:'mixed', dimensions:[['n1','n2'],['n3']-dropped], ... } (n1~n2 share b; n3 disjoint)
27
+ */
28
+
29
+ // Union-find over the item ids, edges = share ≥ minShared witnesses (optionally never across `side`).
30
+ function clusterByGrounding( items, opts ) {
31
+ opts = opts || {};
32
+ const minShared = opts.minShared != null ? opts.minShared : 1;
33
+ const bySide = !!opts.bySide;
34
+ const parent = new Map();
35
+ const find = ( x ) => { while ( parent.get(x) !== x ) { parent.set(x, parent.get(parent.get(x))); x = parent.get(x); } return x; };
36
+ for ( const it of items ) parent.set(it.id, it.id);
37
+ let edges = 0;
38
+ for ( let i = 0; i < items.length; i++ ) for ( let j = i + 1; j < items.length; j++ ) {
39
+ const A = items[i], B = items[j];
40
+ if ( bySide && A.side && B.side && A.side !== B.side ) continue;
41
+ const wb = new Set(B.witnesses || []);
42
+ const shared = (A.witnesses || []).filter(( w ) => wb.has(w) ).length;
43
+ if ( shared >= minShared ) { const ra = find(A.id), rb = find(B.id); if ( ra !== rb ) { parent.set(ra, rb); edges++; } }
44
+ }
45
+ const groups = new Map();
46
+ for ( const it of items ) { const r = find(it.id); if ( !groups.has(r) ) groups.set(r, []); groups.get(r).push(it.id); }
47
+ const clusters = [...groups.values()].map(( g ) => g.slice().sort() ).sort(( a, b ) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0 );
48
+ return { clusters, separability: { components: clusters.length, singletons: clusters.filter(( c ) => c.length === 1 ).length,
49
+ sizes: clusters.map(( c ) => c.length ).sort(( a, b ) => b - a ), edges } };
50
+ }
51
+
52
+ /**
53
+ * Classify a set of grounded items into a FRAME verdict for the re-plan (the `frame: TOO-NARROW` signal):
54
+ * 'coherent' — one connected grounded block → a single dimension → decide normally (no re-plan).
55
+ * 'mixed' — ≥2 grounded groups (size ≥ minGroup) → the frame conflates dimensions → SEPARATE them
56
+ * (the moral⊥legal case, detected structurally, grounded — the re-plan's re-split seed).
57
+ * 'unstructured' — no grounded groups (all singletons; sparse co-citation) → ESCALATE to Q2 to propose
58
+ * dimensions (the lazy-régime GAP), seeded by the singletons; never fabricate a split.
59
+ * @param items [{ id, witnesses:[argId], side? }]
60
+ * @returns { frame, dimensions:[[id..]], singletons:[[id]..], separability, reason }
61
+ */
62
+ function arbitrate( items, opts ) {
63
+ opts = opts || {};
64
+ const minGroup = opts.minGroup != null ? opts.minGroup : 2;
65
+ const { clusters, separability } = clusterByGrounding(items, opts);
66
+ const dimensions = clusters.filter(( c ) => c.length >= minGroup );
67
+ const singletons = clusters.filter(( c ) => c.length < minGroup );
68
+ let frame, reason;
69
+ if ( dimensions.length >= 2 ) { frame = 'mixed';
70
+ reason = dimensions.length + ' grounded groups share no witnesses across groups — the frame conflates ' + dimensions.length + ' dimensions'; }
71
+ else if ( dimensions.length === 1 && !singletons.length ) { frame = 'coherent';
72
+ reason = 'one connected grounded block — a single dimension'; }
73
+ else if ( dimensions.length === 1 ) { frame = 'coherent';
74
+ reason = 'one grounded group' + (singletons.length ? ' + ' + singletons.length + ' ungrounded singleton(s)' : ''); }
75
+ else { frame = 'unstructured';
76
+ reason = 'no grounded group (co-citation too sparse) — escalate to Q2 to propose dimensions'; }
77
+ return { frame, dimensions, singletons, separability, reason };
78
+ }
79
+
80
+ module.exports = { clusterByGrounding, arbitrate };
@@ -0,0 +1,108 @@
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
+ * graph-net — a SHARED-WEIGHT message-passing population over an arbitrary graph: ONE node-update
21
+ * rule (a gate-NN × update-NN) applied at every node, reading a permutation-invariant aggregate of
22
+ * its neighbours, run to a FIXPOINT, trained by the same implicit-diff/DEQ machinery (equilibrium.js).
23
+ * A quantized equilibrium GNN — the substrate generalised from concept-net's fixed structures to an
24
+ * actual graph, so it (a) handles real graph problems where recurrence is essential and (b)
25
+ * GENERALISES to new graphs (one rule everywhere = the distillation payoff). Host-side, ZERO-CORE.
26
+ * Study 2026-06-26 (see doc/concept-learning.md); the rung after the population substrate.
27
+ *
28
+ * Unit: z'[i] = gate_i · cand_i, both σ over ctx_i = [ mean(neighbour states), own state ]; weights
29
+ * SHARED across nodes (θ = 6 numbers, any graph size). Seed-initialised: settle starts from z0.
30
+ *
31
+ * CLAMPED nodes (`opts.clamp`) are held fixed at their z0 value — the "grounded" anchors for a
32
+ * SEED-ANCHORED propagation problem (e.g. known types propagating across a dependency graph). This
33
+ * matters: an UNCLAMPED equilibrium GNN OVERSMOOTHS to the consensus fixpoint (washes out the seed →
34
+ * cannot do symmetry-breaking labelling, finding #26); a clamp breaks the symmetry so the fixpoint is
35
+ * seed-determined and learnable. The DEQ adjoint runs on the FREE (non-clamped) subspace (a clamped
36
+ * node is an identity J_z row → I−J_z singular).
37
+ *
38
+ * const gp = graphPopulation(n, edges, { clamp: [0] }); // node 0 = the grounded source
39
+ * const r = trainGraph(gp, episodes, { steps, lr }); // episodes: [{ z0, target }]
40
+ * const z = gp.settle(r.theta, z0).z; // propagate a NEW graph, no oracle
41
+ */
42
+ const { solveFixpoint, implicitGrad, numJac, spectralRadius } = require('./equilibrium.js');
43
+
44
+ const sigmoid = (x) => 1 / (1 + Math.exp(-x));
45
+
46
+ function graphPopulation( n, edges, opts ) {
47
+ opts = opts || {};
48
+ const clamp = new Set(opts.clamp || []);
49
+ const free = []; for ( let i = 0; i < n; i++ ) if ( !clamp.has(i) ) free.push(i);
50
+ const nb = Array.from({ length: n }, () => []);
51
+ for ( const [a, b] of (edges || []) ) { nb[a].push(b); nb[b].push(a); }
52
+ const nParams = 6; // [Wg_agg, Wg_self, bg, Wu_agg, Wu_self, bu] — SHARED
53
+ function F( z, th ) {
54
+ const zn = z.slice();
55
+ for ( const i of free ) { // clamped nodes keep z[i]
56
+ const ne = nb[i]; let agg = 0;
57
+ for ( const j of ne ) agg += z[j];
58
+ agg = ne.length ? agg / ne.length : 0;
59
+ const self = z[i];
60
+ zn[i] = sigmoid(th[0] * agg + th[1] * self + th[2]) * sigmoid(th[3] * agg + th[4] * self + th[5]);
61
+ }
62
+ return zn;
63
+ }
64
+ const scatter = (zf, z0) => { const z = z0.slice(); free.forEach((j, c) => { z[j] = zf[c]; }); return z; }; // clamped from z0
65
+ const gather = (z) => free.map((j) => z[j]);
66
+ const freeStep = (zf, z0, th) => gather(F(scatter(zf, z0), th)); // the reduced sweep the DEQ differentiates
67
+ return {
68
+ n, nParams, F, neighbors: nb, clamp: [...clamp], free, scatter, gather, freeStep,
69
+ settle( th, z0, o ) { return solveFixpoint((z) => F(z, th), z0.slice(), { maxIter: (o && o.maxIter) || 3000, tol: (o && o.tol) || 1e-12 }); },
70
+ settleFree( th, z0, o ) { const r = solveFixpoint((zf) => freeStep(zf, z0, th), gather(z0), { maxIter: (o && o.maxIter) || 3000, tol: (o && o.tol) || 1e-12 }); return { zfree: r.z, z: scatter(r.z, z0), iters: r.iters, converged: r.converged }; },
71
+ randomParams( rng, scale ) { rng = rng || Math.random; scale = scale == null ? 1 : scale; const t = new Array(nParams); for ( let i = 0; i < nParams; i++ ) t[i] = (rng() * 2 - 1) * scale; return t; },
72
+ };
73
+ }
74
+
75
+ /** Mean-squared per-FREE-node loss over episodes [{ z0, target }]. */
76
+ function graphLoss( gp, th, episodes ) {
77
+ let s = 0, cnt = 0;
78
+ for ( const ep of episodes ) { const z = gp.settle(th, ep.z0).z; for ( const i of gp.free ) { s += (z[i] - ep.target[i]) ** 2; cnt++; } }
79
+ return s / cnt;
80
+ }
81
+
82
+ /** Implicit-diff (DEQ) gradient of graphLoss wrt the shared θ (over the free subspace), summed. */
83
+ function graphGrad( gp, th, episodes ) {
84
+ const g = new Array(gp.nParams).fill(0), N = episodes.length * gp.free.length;
85
+ for ( const ep of episodes ) {
86
+ const r = gp.settleFree(th, ep.z0), zf = r.zfree;
87
+ const gl = zf.map((v, c) => 2 * (v - ep.target[gp.free[c]]) / N);
88
+ const Jz = numJac((zz) => gp.freeStep(zz, ep.z0, th), zf), Jt = numJac((tt) => gp.freeStep(zf, ep.z0, tt), th);
89
+ const gr = implicitGrad(Jz, Jt, gl, { mode: 'direct' });
90
+ for ( let k = 0; k < gp.nParams; k++ ) g[k] += gr.grad[k];
91
+ }
92
+ return g;
93
+ }
94
+
95
+ /** Train the shared node-rule on episodes (Adam). @returns { theta, loss0, loss }. */
96
+ function trainGraph( gp, episodes, conf ) {
97
+ conf = conf || {};
98
+ const steps = conf.steps || 1500, lr = conf.lr || 0.05;
99
+ let th = (conf.theta0 || gp.randomParams(conf.rng, conf.initScale)).slice();
100
+ const loss0 = graphLoss(gp, th, episodes), mm = th.map(() => 0), vv = th.map(() => 0), b1 = 0.9, b2 = 0.999;
101
+ for ( let s = 1; s <= steps; s++ ) {
102
+ const gr = graphGrad(gp, th, episodes);
103
+ for ( let k = 0; k < th.length; k++ ) { mm[k] = b1 * mm[k] + (1 - b1) * gr[k]; vv[k] = b2 * vv[k] + (1 - b2) * gr[k] * gr[k]; th[k] -= lr * (mm[k] / (1 - b1 ** s)) / (Math.sqrt(vv[k] / (1 - b2 ** s)) + 1e-8); }
104
+ }
105
+ return { theta: th, loss0, loss: graphLoss(gp, th, episodes) };
106
+ }
107
+
108
+ module.exports = { graphPopulation, graphLoss, graphGrad, trainGraph };