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,114 @@
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
+ * segment-proxy — THE SEGMENT-PROXY RÉACTIF (roadmap 2026-07-09 P2; ZERO-CORE, host-side). A concept-method mounted
10
+ * as a reactive PROXY: it casts on its contract's cast-conditions, and AT CAST delegates its body to another instance
11
+ * via the P1 bounded invoke (instead of expanding inline), ADMITS the bounded result through the P4 gate
12
+ * (`assertPost`), and posts the summaryFacts as its own cast → JTMS-visible. The good C.8: an INTERFACE, not an
13
+ * in-core COW object (KG-PROXY C proved the posted facts are re-evaluable, not a severing read-gate).
14
+ *
15
+ * The cast is the COST GRADIENT (contract.js §0.1): (1) delegate to the library method (the invoke), (2) gate the
16
+ * result (assertPost), (3) LAST-RESORT LEARNING FALLBACK — on a hole / a gate refusal, an `forge` hook receives the
17
+ * problem STACK reconstituted UP the parent chain (`reconstructStack`, BOUNDED to each level's typed interface — not
18
+ * the prose) and may forge a method / adjust the slot qualification via the lattice. The forged method RE-ENTERS the
19
+ * gate (propose→assertPost disposes→index back) — it is typed sub-graph, so determinism holds. The full `adapt`/
20
+ * `crystallize` wiring of the fallback is the §5 plasticity layer; P2 provides the HOOK + the gate + the stack.
21
+ *
22
+ * Within one synchronous cast there is no extract→merge window, so the P4 assumption-recheck is not needed here (it
23
+ * guards the async host-side flow); P2 reuses P4's assertPost GATE and returns the cast template via `cb` (the engine
24
+ * applies it sequenced). GOTCHA respected: the template sets the proxy's own `<name>:true` cast marker.
25
+ */
26
+ const { assertPost } = require('./contract.js');
27
+
28
+ /**
29
+ * reconstructStack(graph, scope, opts) — reconstitute the bounded problem STACK by walking UP the `parentSeg` chain
30
+ * (the projection sets `parentSeg` on every seeded segment). Collects each level's TYPED interface only (statement +
31
+ * produces/needs), bounded by `maxDepth` — the reconstituted abstract context for a forge, WITHOUT the O(N) prose
32
+ * blow-up (garde-fou 1: the separator-typed interface travels, not the raw context).
33
+ * @returns [{ id, statement, produces, needs }] (self first, root last)
34
+ */
35
+ function reconstructStack( graph, scope, opts ) {
36
+ opts = opts || {};
37
+ const max = opts.maxDepth || 8;
38
+ const stack = [], seen = new Set();
39
+ let cur = scope && scope._, depth = 0;
40
+ while ( cur && depth <= max ) {
41
+ if ( cur._id == null || seen.has(cur._id) ) break;
42
+ seen.add(cur._id);
43
+ stack.push({ id: cur._id, statement: cur.statement, produces: cur.produces, needs: cur.needs });
44
+ const pid = cur.parentSeg;
45
+ if ( pid == null ) break;
46
+ const pe = graph.getEtty ? graph.getEtty(pid) : null;
47
+ cur = pe && pe._; depth++;
48
+ }
49
+ return stack;
50
+ }
51
+
52
+ /**
53
+ * makeSegmentProxy(spec) — build a reactive proxy concept + its provider.
54
+ * @param spec.name the proxy concept name (its cast marker).
55
+ * @param spec.ns provider namespace (default 'Proxy').
56
+ * @param spec.castWhen extra require facts (besides Segment) that gate the cast (the contract's cast-conditions).
57
+ * @param spec.contract { read, write, pre, post, effect } — the method's typed contract (the P4 gate).
58
+ * @param spec.methodMap the concept map of the DELEGATE (the shared instance runs it).
59
+ * @param spec.buildSeed (scope, graph) => seed build the invoke seed from the cast segment (the slot bindings).
60
+ * @param spec.boundedFrom the delegate object id whose facts are the summary.
61
+ * @param spec.boundedKeys Σ_sep — the frontier alphabet to cross (default = contract.write).
62
+ * @param spec.invoke (opts) => Promise<{summary,writeFootprint}> the P1 invoke (default Graph.invokeGraph).
63
+ * @param spec.oracle G2 ground-truth probe for an effecting post.
64
+ * @param spec.forge ({scope, graph, contract, stack, reason, blame?}) => Promise<template>|template the
65
+ * last-resort learning fallback (§5). Receives the reconstituted parent stack.
66
+ * @returns { conceptFragment, provider, name }
67
+ */
68
+ function makeSegmentProxy( spec ) {
69
+ spec = spec || {};
70
+ const name = spec.name || 'Proxy';
71
+ const ns = spec.ns || 'Proxy';
72
+ const contract = spec.contract || {};
73
+ const boundedKeys = spec.boundedKeys || contract.write || [];
74
+ // invoke = the P1 wire. With a P3 pool, key it by libraryKey → N casts reuse ONE warm instance; else one-shot invokeGraph.
75
+ const libraryKey = spec.libraryKey || name;
76
+ const invoke = spec.invoke || (spec.pool ? ( iopts ) => spec.pool.invoke(libraryKey, iopts) : require('../index.js').invokeGraph);
77
+
78
+ const conceptFragment = { [name]: { _id: name, _name: name,
79
+ require: ['Segment'].concat(spec.castWhen || []), provider: [ ns + '::delegate' ] } };
80
+
81
+ function delegate( graph, concept, scope, argz, cb ) {
82
+ (async () => {
83
+ // the fallback: reconstitute the stack from the parents, forge under the gate; else a typed refusal.
84
+ const fallback = async ( reason, blame ) => {
85
+ if ( typeof spec.forge === 'function' ) {
86
+ const stack = reconstructStack(graph, scope, spec);
87
+ const t = await spec.forge({ scope, graph, contract, stack, reason, blame });
88
+ return t; // the forge returns a cast template (typed sub-graph)
89
+ }
90
+ return [ { $_id: '_parent', [name]: true, proxyRefused: (blame && blame.kind) || reason } ];
91
+ };
92
+
93
+ let seed;
94
+ try { seed = spec.buildSeed(scope, graph); }
95
+ catch ( e ) { return cb(null, await fallback('build-failed', { kind: e.message })); }
96
+
97
+ // (1) DELEGATE via the P1 bounded invoke to another instance (not expand inline).
98
+ let result;
99
+ try { result = await invoke({ conceptMap: spec.methodMap, seed, boundedFrom: spec.boundedFrom, boundedKeys, settleTimeout: spec.settleTimeout }); }
100
+ catch ( e ) { return cb(null, await fallback('invoke-failed', { kind: e.message })); }
101
+
102
+ // (2) GATE the bounded result (P4 assertPost: G1 frame via the write-footprint · post-holds · G2 oracle).
103
+ const post = assertPost(contract, result.summary || {}, result.writeFootprint || [], { oracle: spec.oracle });
104
+ if ( !post.ok ) return cb(null, await fallback('gate-refused', post.blame)); // (3) last-resort learning fallback
105
+
106
+ // admit — post the summaryFacts as this proxy's cast (JTMS-visible) + the cast marker.
107
+ cb(null, [ Object.assign({ $_id: '_parent', [name]: true }, result.summary || {}) ]);
108
+ })().catch(( e ) => cb(e));
109
+ }
110
+
111
+ return { conceptFragment, provider: { [ns]: { delegate } }, name };
112
+ }
113
+
114
+ module.exports = { makeSegmentProxy, reconstructStack };
@@ -0,0 +1,64 @@
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
+ * serve-leaf — THE UNIFICATION (roadmap 2026-07-09 P6; ZERO-CORE). `context-project`'s projection serves each leaf
10
+ * with an injected `serve(leaf)` — until now an opaque stub. P6 makes **`serveLeaf` = DISPATCH (libraryKey) + MOUNT
11
+ * (a concept-method invoked on a shared instance)**: a leaf is no longer an opaque value, it IS a mounted method. This
12
+ * folds projection + runtime (P1 invoke) + library (dispatch) + method (the contract gate) into ONE structure — "le
13
+ * reste s'adapte à la structure centrale", by the constat.
14
+ *
15
+ * const serve = makeMethodServe({ methods, pool }); // pool = a P3 invoke-pool (N cases → 1 instance)
16
+ * const proj = createContextProjection({ serve }); // a leaf IS a dispatched, mounted, gated method
17
+ * const { order, results } = await proj.run(roadmap, ctx);
18
+ *
19
+ * Per leaf: (1) DISPATCH — `keyOf(leaf)` → a libraryKey (default: leaf.produces; library.js#byKey in production);
20
+ * (2) MOUNT + INVOKE — the method runs on its shared warm instance (P3) with the leaf's bounded inputs as the seed
21
+ * (the slot bindings); (3) GATE — assertPost (P4) admits the bounded summary or refuses (blame → the §5 forge fallback);
22
+ * the leaf's VALUE is the method's bounded output, which flows to downstream leaves through the projection's pool.
23
+ */
24
+ const { assertPost } = require('./contract.js');
25
+
26
+ /**
27
+ * makeMethodServe(spec) — a projection `serve(leaf, ctx)` that dispatches+mounts a concept-method per leaf.
28
+ * @param spec.methods { <libraryKey>: { conceptMap, providers?, contract, boundedFrom, boundedKeys?, buildSeed(leaf,ctx), value?(summary), oracle? } }
29
+ * @param spec.keyOf (leaf) => libraryKey the dispatch (default: leaf.produces || leaf.id).
30
+ * @param spec.pool a P3 invoke-pool (default: a fresh own pool — close via serve.close()).
31
+ * @param spec.fallback (leaf, ctx, info) => value the §5 last-resort on a dispatch miss / a gate refusal.
32
+ * @returns serve async (leaf, ctx) => value (+ serve.pool, serve.close()).
33
+ */
34
+ function makeMethodServe( spec ) {
35
+ spec = spec || {};
36
+ const methods = spec.methods || {};
37
+ const keyOf = spec.keyOf || (( leaf ) => leaf.produces || leaf.id);
38
+ const pool = spec.pool || require('../index.js').createInvokePool();
39
+ const ownPool = !spec.pool;
40
+
41
+ async function serve( leaf, ctx ) {
42
+ const key = keyOf(leaf); // (1) DISPATCH
43
+ const m = methods[key];
44
+ if ( !m ) {
45
+ if ( spec.fallback ) return spec.fallback(leaf, ctx, { reason: 'no-method', key });
46
+ throw new Error('serveLeaf: no method for libraryKey "' + key + '" (leaf ' + leaf.id + ')');
47
+ }
48
+ const boundedKeys = m.boundedKeys || (m.contract && m.contract.write) || [];
49
+ const seed = m.buildSeed(leaf, ctx); // the leaf's bounded inputs = the slot bindings
50
+ const result = await pool.invoke(key, { conceptMap: m.conceptMap, providers: m.providers, // (2) MOUNT + INVOKE (P1/P3)
51
+ seed, boundedFrom: m.boundedFrom, boundedKeys, settleTimeout: m.settleTimeout });
52
+ const post = assertPost(m.contract || {}, result.summary || {}, result.writeFootprint || [], { oracle: m.oracle }); // (3) GATE (P4)
53
+ if ( !post.ok ) {
54
+ if ( spec.fallback ) return spec.fallback(leaf, ctx, { reason: 'gate-refused', blame: post.blame });
55
+ throw new Error('serveLeaf: gate refused for "' + key + '": ' + post.violations.map(( v ) => v.kind).join(','));
56
+ }
57
+ return m.value ? m.value(result.summary, leaf) : result.summary; // the leaf's VALUE = the mounted method's bounded output
58
+ }
59
+ serve.pool = pool;
60
+ serve.close = () => ownPool ? pool.close() : Promise.resolve();
61
+ return serve;
62
+ }
63
+
64
+ module.exports = { makeMethodServe };
@@ -0,0 +1,77 @@
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
+ * slot-aware-serve — THE ROUTER between a plain leaf and a higher-order (method-slot) leaf (roadmap §5(a), the
10
+ * GENERATION seam; ZERO-CORE). `dag-decompose` now EMITS a typed `slot:{over,body,combinator}` on a part; this serve
11
+ * inspects it and forks:
12
+ * • NO slot → `makeMethodServe` (P6): dispatch → mount → gate a concept-method (a leaf IS a mounted method).
13
+ * • a typed slot → `makeHigherOrderServe` (§5a): the slot's `body` is DISPATCHED over the items resolved under
14
+ * `over`, reduced by `combinator` (map|all|any) — a leaf IS a mounted loop-of-methods.
15
+ * Both forks SHARE one P3 invoke-pool (N cases → 1 instance). The items come from the projection's RESOLVED bounded
16
+ * context (`leaf.inputs[over]`, because `over` was auto-added as a need) — so the loop is never vacuous: an UNRESOLVED
17
+ * `over` (or a non-array value) is a typed refusal (→ fallback), NEVER `[].every(Boolean)===true` (the confront break).
18
+ *
19
+ * const serve = makeSlotAwareServe({ methods, bodies }); // methods = plain library, bodies = slot fillers
20
+ * const proj = createContextProjection({ serve }); // plain + higher-order leaves, one projection
21
+ * const { results } = await proj.run(leavesToRoadmap(await decompose(task)), ctx);
22
+ */
23
+ const { makeMethodServe } = require('./serve-leaf.js');
24
+ const { makeHigherOrderServe } = require('./higher-order.js');
25
+
26
+ // read the method-slot off a leaf, from either channel: the projection-reconstructed leaf (`leaf.slot`) or the
27
+ // plan-loop-carried leaf (`leaf.request.slot`).
28
+ function slotOf( leaf ) { return (leaf && (leaf.slot || (leaf.request && leaf.request.slot))) || null; }
29
+
30
+ /**
31
+ * makeSlotAwareServe(spec) — a projection `serve(leaf, ctx)` that routes plain vs higher-order leaves.
32
+ * @param spec.methods { <libraryKey>: <makeMethodServe method spec> } the PLAIN library (dispatched per leaf).
33
+ * @param spec.bodies { <bodyKey>: <makeMethodServe method spec> } the SLOT FILLERS (dispatched per item).
34
+ * @param spec.keyOf (leaf) => libraryKey the plain dispatch (default: leaf.produces || leaf.id).
35
+ * @param spec.pool a shared P3 invoke-pool (default: a fresh own pool — close via serve.close()).
36
+ * @param spec.fallback (leaf, ctx, info) => value the §5 last-resort on a dispatch miss / an unresolved slot.
37
+ * @returns serve async (leaf, ctx) => value (+ serve.pool, serve.close()).
38
+ */
39
+ function makeSlotAwareServe( spec ) {
40
+ spec = spec || {};
41
+ const bodies = spec.bodies || {};
42
+ const pool = spec.pool || require('../index.js').createInvokePool();
43
+ const ownPool = !spec.pool;
44
+ // dispatch key, ROBUST to both leaf shapes: the plan-loop leaf keys on `request.id` (its id is prefixed `n_<key>`),
45
+ // the à-nu projection leaf keys on `produces`. Without this a plan-loop leaf mis-dispatches → provider error →
46
+ // the Step concept re-fires to the apply-cap (1000) → DIVERGENT (the cast-marker GOTCHA). Default covers both.
47
+ const keyOf = spec.keyOf || (( l ) => (l.request && l.request.id) || l.produces || l.id);
48
+ const plain = makeMethodServe({ methods: spec.methods || {}, keyOf: keyOf, pool: pool, fallback: spec.fallback });
49
+ // three generic combinator-keyed loops → the emitted slot maps 1:1 onto `makeHigherOrderServe` (ZERO consumer delta):
50
+ // bodyKeyOf/items are DATA-DRIVEN off the slot, so the same three loops serve every emitted higher-order leaf.
51
+ const mkLoop = ( combinator ) => ({ combinator: combinator,
52
+ bodyKeyOf: ( l ) => slotOf(l).body, items: ( l ) => (l.inputs || {})[slotOf(l).over] });
53
+ // the 3 generic combinator loops + any NAMED nested loops (spec.loops) → a slot whose `body` names a nested loop
54
+ // (map-of-maps) recurses inside makeHigherOrderServe.
55
+ const nested = spec.loops || {};
56
+ const loops = Object.assign({ __slot_map: mkLoop('map'), __slot_all: mkLoop('all'), __slot_any: mkLoop('any') }, nested);
57
+ const ho = makeHigherOrderServe({ bodies: bodies, pool: pool, loops: loops, keyOf: ( l ) => '__slot_' + slotOf(l).combinator });
58
+
59
+ function refuse( leaf, ctx, info ) {
60
+ if ( spec.fallback ) return spec.fallback(leaf, ctx, info);
61
+ throw new Error('slot-aware serve: ' + info.reason + ' for slot on leaf ' + (leaf && leaf.id) + (info.over ? ' (over="' + info.over + '")' : ''));
62
+ }
63
+
64
+ async function serve( leaf, ctx ) {
65
+ const slot = slotOf(leaf);
66
+ if ( !slot ) return plain(leaf, ctx); // PLAIN → dispatch+mount+gate a method
67
+ if ( !bodies[slot.body] && !nested[slot.body] ) return refuse(leaf, ctx, { reason: 'no-body', body: slot.body }); // dispatch miss (a plain body OR a nested loop)
68
+ const items = (leaf.inputs || {})[slot.over]; // the items = the RESOLVED bounded input
69
+ if ( !Array.isArray(items) ) return refuse(leaf, ctx, { reason: 'slot-unresolved', over: slot.over }); // never iterate undefined/a string
70
+ return ho(leaf, ctx); // HIGHER-ORDER → dispatched loop over items
71
+ }
72
+ serve.pool = pool;
73
+ serve.close = () => ownPool ? pool.close() : Promise.resolve();
74
+ return serve;
75
+ }
76
+
77
+ module.exports = { makeSlotAwareServe, slotOf };
@@ -0,0 +1,59 @@
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
+ * sound-invoke — THE CONSTAT OPERATIONALIZED (roadmap 2026-07-09 P4; ZERO-CORE, host-side). KG-PROXY proved that a
10
+ * bounded cross-instance invoke ≡ the flat run — same result + same JTMS at drift. P1 gave the bounded WIRE
11
+ * (`invoke` → `{ summary, writeFootprint }`, only Σ_sep crosses). P4 makes the ADMISSION of that result SOUND, so the
12
+ * equivalence is not just observed on a happy path but ENFORCED — "composé-cross-instance ≡ plat, *sainement*".
13
+ *
14
+ * `soundInvokeMerge(parent, invokeResult, extraction, { contract, targetId, oracle? })` gates the commit on the two
15
+ * things the constat needs across the frontier, then applies through the sequenced taskflow:
16
+ *
17
+ * 1. assertPost(contract, summary, writeFootprint) — the induced post asserted at ADMISSION (contract.js):
18
+ * · G1 FRAME-COMPLETENESS — the keys the body actually wrote (the P1 write-footprint = the delta) ⊆ declared
19
+ * write. An under-declared frame is the silent unsoundness; the write-footprint from P1 is exactly its witness.
20
+ * · the POST holds on the returned summaryFacts (the induced hypothesis — sound on observed, asserted here).
21
+ * · G2 EFFECT-TAG — an external/irreversible method's post needs a ground-truth ORACLE, never the internal fact.
22
+ * A violation → REFUSE (the caller never commits a lying result) + `blame` (feed `reviseOnBlame`, CEGIS).
23
+ * 2. mergeSlice (extract.js) — the ASSUMPTION-RECHECK (the frozen frontier still holds in the parent since extraction
24
+ * — the ATMS defeasance) + SINGLE-WRITER (never writes a frontier id) + `pushMutation` (sequenced). A frontier
25
+ * drift → REFUSE (the dead-premise result is not committed — the sound analog of the flat JTMS retract).
26
+ *
27
+ * On success the summaryFacts are posted as FIRST-CLASS re-evaluable facts (KG-PROXY C): the caller's JTMS carries the
28
+ * ongoing soundness — a LATER premise drift retracts them like any flat fact. ⇒ composé-cross-instance ≡ plat, sainement.
29
+ */
30
+ const { assertPost } = require('./contract.js');
31
+ const { mergeSlice } = require('./extract.js');
32
+
33
+ /**
34
+ * @param parent the caller graph.
35
+ * @param invokeResult { summary:{<Σ_sep facts>}, writeFootprint:[keys] } — from Graph.invokeGraph / worker `invoke`.
36
+ * @param extraction the extractSubgraph record (carries the frozen frontier for the assumption-recheck).
37
+ * @param opts.contract { read, write, pre, post, effect } — the method's typed contract.
38
+ * @param opts.targetId the caller object the bounded summary posts onto (an interior id — never a frozen frontier id).
39
+ * @param opts.oracle (contract, factsAfter) => bool — ground-truth probe for an effecting post (G2).
40
+ * @returns { merged:true, template } | { merged:false, reason, blame? }
41
+ */
42
+ function soundInvokeMerge( parent, invokeResult, extraction, opts ) {
43
+ opts = opts || {};
44
+ const summary = (invokeResult && invokeResult.summary) || {};
45
+ const footprint = (invokeResult && invokeResult.writeFootprint) || [];
46
+ const contract = opts.contract || {};
47
+
48
+ // (1) post-assert + blame — G1 frame / post-holds / G2 oracle, at admission.
49
+ const post = assertPost(contract, summary, footprint, { oracle: opts.oracle });
50
+ if ( !post.ok )
51
+ return { merged: false, reason: 'post-violation: ' + post.violations.map(( v ) => v.kind + (v.detail != null ? '(' + v.detail + ')' : '')).join(','), blame: post.blame };
52
+
53
+ // (2) assumption-recheck (frontier) + single-writer + sequenced apply. The write template IS the bounded summary
54
+ // (already Σ_sep — no child to project from), posted onto the interior target.
55
+ const project = () => [ Object.assign({ $$_id: opts.targetId }, summary) ];
56
+ return mergeSlice(parent, null, extraction, { project });
57
+ }
58
+
59
+ module.exports = { soundInvokeMerge };
@@ -0,0 +1,74 @@
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
+ * makeSplitServe — the RECURSIVE composite path (KG-SPLIT GO, 2026-07-10). Single-shot emission of a COMPOSED
10
+ * shape was REFUTED live (Q6 oriented: 0% strict / 14% relaxed — `2026-07-09-reachability-composed`); what works
11
+ * is the recursive chain, proven live on the same corpus (55% strict / 64% relaxed / 86% kind / 0% plain
12
+ * false-positives — `2026-07-10-recursive-split`):
13
+ *
14
+ * kind-route (SET-OP / NESTED / PLAIN) → SPLIT the NL question into STANDALONE sub-questions →
15
+ * plain-decompose EACH sub-question with the proven oriented emission → assemble `operand|op`.
16
+ *
17
+ * The split is where the lift lives: a standalone sub-question decomposes at the plain rate (~78%) because the
18
+ * sub-query no longer LEAKS into the outer reading and the certified-atom menu no longer has to steer a composite
19
+ * question. This is what makes the COMPOSED certified vocabulary (blend/combine classes, KG-6) DISPATCHABLE — the
20
+ * named lever for growing the trusted tier.
21
+ *
22
+ * The brick owns NO model (à-nu doctrine): `split` and `plain` are injected. FAIL-CLOSED routing: a malformed,
23
+ * conflicting, or throwing split falls back to the plain path on the ORIGINAL query (tagged `fallback:true`) —
24
+ * the router never invents a composition and never blocks a query. Honest residual (do NOT claim it): the NESTED
25
+ * outer-with-VALUE-placeholder decomposes at ~2/6 live; set-ops are the proven half (intersect 6/6 strict).
26
+ *
27
+ * const sv = makeSplitServe({ split, plain });
28
+ * const r = await sv.serve(query); // { shape, kind:'plain'|'setop'|'nested', parts?, sameOperand?, fallback? }
29
+ *
30
+ * @param opts.split REQUIRED async (query) => { setop:'none'|'intersect'|'except'|'union', nested:bool, q1, q2 }
31
+ * — the NL splitter (an LLM call; see the kill-gate's SYS_SPLIT prompt + grammar).
32
+ * @param opts.plain REQUIRED async (question) => shape — the PROVEN oriented plain decomposer (menu-glossed,
33
+ * canonical-order prompt; the grammar is format-insurance only — signature-stability finding).
34
+ * @param opts.ops optional array — accepted set-ops (default ['intersect','except','union']).
35
+ * @returns {{ serve, stats }}
36
+ */
37
+ function makeSplitServe( opts ) {
38
+ opts = opts || {};
39
+ if ( typeof opts.split !== 'function' ) throw new Error('makeSplitServe needs opts.split (async query -> {setop, nested, q1, q2})');
40
+ if ( typeof opts.plain !== 'function' ) throw new Error('makeSplitServe needs opts.plain (async question -> shape) — the proven plain decomposer');
41
+ var OPS = Array.isArray(opts.ops) && opts.ops.length ? opts.ops : ['intersect', 'except', 'union'];
42
+ var stats = { served: 0, plain: 0, setop: 0, nested: 0, fallback: 0 };
43
+
44
+ async function plainFallback( query ) {
45
+ stats.plain++; stats.fallback++;
46
+ return { shape: await opts.plain(query), kind: 'plain', fallback: true };
47
+ }
48
+
49
+ async function serve( query ) {
50
+ stats.served++;
51
+ var k;
52
+ try { k = await opts.split(query) || {}; } catch ( _e ) { return plainFallback(query); }
53
+ var isSet = OPS.indexOf(k.setop) >= 0;
54
+ // fail-closed routing: conflicting (setop AND nested) or malformed (set-op without both sides) → plain.
55
+ if ( (isSet && k.nested) || (isSet && !(k.q1 && k.q2)) || (k.nested && !k.q1) ) return plainFallback(query);
56
+ if ( isSet ) {
57
+ var s1 = await opts.plain(String(k.q1));
58
+ var s2 = await opts.plain(String(k.q2));
59
+ stats.setop++;
60
+ return { shape: s1 + '|' + k.setop, kind: 'setop', sameOperand: s1 === s2, parts: { q1: k.q1, q2: k.q2, s1: s1, s2: s2 } };
61
+ }
62
+ if ( k.nested ) {
63
+ var outer = await opts.plain(String(k.q1));
64
+ stats.nested++;
65
+ return { shape: outer + '|n', kind: 'nested', parts: { q1: k.q1, inner: k.q2 || '', s1: outer } };
66
+ }
67
+ stats.plain++;
68
+ return { shape: await opts.plain(query), kind: 'plain' }; // plain: the ORIGINAL query, not q1 (no drift)
69
+ }
70
+
71
+ return { serve: serve, stats: stats };
72
+ }
73
+
74
+ module.exports = { makeSplitServe: makeSplitServe };
@@ -0,0 +1,239 @@
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
+ * Straight-Through Estimator (STE) training for concept-networks (doc/WIP/HANDOFF.md §7 Tier 4;
21
+ * experiment E5). Zero core change — an OFFLINE trainer (run between revisions, not in the cast
22
+ * loop) plus a provider that bakes the learned weights into a concept doing HARD forward
23
+ * inference. Train SOFT (sigmoid + SGD), infer HARD (step/threshold cast) with the same weights:
24
+ * the straight-through estimator, consistent with the canonicalization barrier (a hard discrete
25
+ * cast at inference, continuous only during offline training).
26
+ *
27
+ * The thesis it makes operational (E5, the user's founding intuition): the TOPOLOGY decides what
28
+ * is learnable. A single log-linear unit learns AND but never XOR; adding a hidden layer (depth
29
+ * in the concept topology) turns the unlearnable into the learnable — measured, not asserted.
30
+ *
31
+ * const { trainNet, predictHard, createNet, netConceptTree } = require('./ste');
32
+ * const fit = trainNet(X, Y, { layers: [2, 3, 1], restarts: 8 }); // offline
33
+ * register(Graph, [ createNet(fit.net, { inputKeys: ['x0', 'x1'] }) ]); // bake -> Net::infer (hard)
34
+ *
35
+ * NOT a substitute for the deterministic socle: this trains a bounded feed-forward concept-net
36
+ * offline. Cross-strata credit assignment for continuous weights across the full graph is open R&D.
37
+ */
38
+
39
+ function sigmoid( x ) { return 1 / (1 + Math.exp(-x)); }
40
+ function dSigmoidFromOutput( y ) { return y * (1 - y); } // σ'(z) in terms of a = σ(z)
41
+
42
+ // Initialise an MLP for the given layer sizes [n_in, ..., n_out]; weights/biases in [-1,1].
43
+ function initNet( layers, rng ) {
44
+ rng = rng || Math.random;
45
+ var W = [], b = [];
46
+ for ( var l = 1; l < layers.length; l++ ) {
47
+ var rows = layers[l], cols = layers[l - 1], Wl = [], bl = [];
48
+ for ( var i = 0; i < rows; i++ ) {
49
+ var row = [];
50
+ for ( var j = 0; j < cols; j++ ) row.push(rng() * 2 - 1);
51
+ Wl.push(row);
52
+ bl.push(rng() * 2 - 1);
53
+ }
54
+ W.push(Wl); b.push(bl);
55
+ }
56
+ return { layers: layers.slice(), W: W, b: b };
57
+ }
58
+
59
+ // Soft forward pass; returns the activation vector of every layer (acts[0] = input).
60
+ function forward( net, x ) {
61
+ var acts = [x.slice()];
62
+ for ( var l = 0; l < net.W.length; l++ ) {
63
+ var Wl = net.W[l], bl = net.b[l], a = acts[acts.length - 1], out = [];
64
+ for ( var i = 0; i < Wl.length; i++ ) {
65
+ var z = bl[i];
66
+ for ( var j = 0; j < Wl[i].length; j++ ) z += Wl[i][j] * a[j];
67
+ out.push(sigmoid(z));
68
+ }
69
+ acts.push(out);
70
+ }
71
+ return acts;
72
+ }
73
+
74
+ // One SGD step (MSE loss) on a single example.
75
+ function trainStep( net, x, y, lr ) {
76
+ var acts = forward(net, x), L = net.W.length, deltas = new Array(L);
77
+ var outA = acts[L], dL = [];
78
+ for ( var i = 0; i < outA.length; i++ ) dL.push((outA[i] - y[i]) * dSigmoidFromOutput(outA[i]));
79
+ deltas[L - 1] = dL;
80
+ for ( var l = L - 2; l >= 0; l-- ) {
81
+ var Wnext = net.W[l + 1], dNext = deltas[l + 1], a = acts[l + 1], d = [];
82
+ for ( var iH = 0; iH < a.length; iH++ ) {
83
+ var s = 0;
84
+ for ( var k = 0; k < Wnext.length; k++ ) s += Wnext[k][iH] * dNext[k];
85
+ d.push(s * dSigmoidFromOutput(a[iH]));
86
+ }
87
+ deltas[l] = d;
88
+ }
89
+ for ( var ll = 0; ll < L; ll++ ) {
90
+ var aPrev = acts[ll], Wl = net.W[ll], bl = net.b[ll], dd = deltas[ll];
91
+ for ( var r = 0; r < Wl.length; r++ ) {
92
+ for ( var c = 0; c < Wl[r].length; c++ ) Wl[r][c] -= lr * dd[r] * aPrev[c];
93
+ bl[r] -= lr * dd[r];
94
+ }
95
+ }
96
+ }
97
+
98
+ // Mean squared error over a dataset.
99
+ function mseLoss( net, X, Y ) {
100
+ var s = 0;
101
+ for ( var i = 0; i < X.length; i++ ) {
102
+ var o = forward(net, X[i]).pop();
103
+ for ( var j = 0; j < o.length; j++ ) s += (o[j] - Y[i][j]) * (o[j] - Y[i][j]);
104
+ }
105
+ return s / X.length;
106
+ }
107
+
108
+ /**
109
+ * Train an MLP offline (SOFT — sigmoid + SGD), with random restarts to escape bad inits.
110
+ * @param X inputs (array of number vectors)
111
+ * @param Y targets (array of number vectors)
112
+ * @param opts.layers layer sizes (default [n_in, 2, n_out]); the TOPOLOGY that gates learnability
113
+ * @param opts.epochs passes over the data (default 3000)
114
+ * @param opts.lr learning rate (default 0.5)
115
+ * @param opts.restarts random restarts; keep the best (default 6)
116
+ * @param opts.rng seedable RNG for reproducibility (default Math.random)
117
+ * @returns { net, loss } the lowest-loss net
118
+ */
119
+ function trainNet( X, Y, opts ) {
120
+ opts = opts || {};
121
+ var layers = opts.layers || [X[0].length, 2, Y[0].length],
122
+ epochs = opts.epochs || 3000, lr = opts.lr || 0.5, restarts = opts.restarts || 6, rng = opts.rng || Math.random,
123
+ best = null;
124
+ for ( var r = 0; r < restarts; r++ ) {
125
+ var net = initNet(layers, rng);
126
+ for ( var e = 0; e < epochs; e++ )
127
+ for ( var i = 0; i < X.length; i++ ) trainStep(net, X[i], Y[i], lr);
128
+ var loss = mseLoss(net, X, Y);
129
+ if ( !best || loss < best.loss ) best = { net: net, loss: loss };
130
+ }
131
+ return best;
132
+ }
133
+
134
+ /**
135
+ * Straight-through HARD inference: the SAME learned weights, but every unit is a STEP (z≥0 → 1)
136
+ * instead of a sigmoid — i.e. each layer's activation is snapped (round(σ(z))). This is the cast
137
+ * the engine performs; it equals the rounded soft prediction on a well-separated trained net (E5).
138
+ */
139
+ function predictHard( net, x ) {
140
+ var a = x.slice();
141
+ for ( var l = 0; l < net.W.length; l++ ) {
142
+ var Wl = net.W[l], bl = net.b[l], out = [];
143
+ for ( var i = 0; i < Wl.length; i++ ) {
144
+ var z = bl[i];
145
+ for ( var j = 0; j < Wl[i].length; j++ ) z += Wl[i][j] * a[j];
146
+ out.push(z >= 0 ? 1 : 0);
147
+ }
148
+ a = out;
149
+ }
150
+ return a;
151
+ }
152
+
153
+ /**
154
+ * Like predictHard, but injects exploration noise into each unit's pre-activation z before the
155
+ * step threshold — the inference-time face of PLASTICITY for an NN concept. `noise` scales the
156
+ * uniform perturbation (±noise); `noise = 0` is exactly predictHard (the frozen path). A plastic
157
+ * net thus occasionally explores a different cast on a well-separated unit; a frozen one is
158
+ * deterministic. Offline training uses lr; live inference uses this noise — the same continuous p.
159
+ */
160
+ function predictNoisy( net, x, opts ) {
161
+ opts = opts || {};
162
+ var noise = opts.noise || 0, rng = opts.rng || Math.random;
163
+ var a = x.slice();
164
+ for ( var l = 0; l < net.W.length; l++ ) {
165
+ var Wl = net.W[l], bl = net.b[l], out = [];
166
+ for ( var i = 0; i < Wl.length; i++ ) {
167
+ var z = bl[i];
168
+ for ( var j = 0; j < Wl[i].length; j++ ) z += Wl[i][j] * a[j];
169
+ if ( noise ) z += (rng() * 2 - 1) * noise;
170
+ out.push(z >= 0 ? 1 : 0);
171
+ }
172
+ a = out;
173
+ }
174
+ return a;
175
+ }
176
+
177
+ /**
178
+ * Bake a trained net into a provider fragment (host opt-in). The provider reads the input facts
179
+ * and writes the HARD prediction — offline-trained weights, hard inference in the engine.
180
+ * @param net a trained net (from trainNet().net)
181
+ * @param opts.inputKeys default input fact keys (per-concept override via the concept's `net`)
182
+ * @param opts.plasticity (conceptName)->p∈[0,1] the UNIFIED plasticity knob (e.g. a
183
+ * `lifecycle.plasticity` accessor). p=0 (frozen) → deterministic predictHard; p>0
184
+ * (plastic) → predictNoisy with noise = noiseScale·p (explore). Unwired → frozen.
185
+ * DISCIPLINE: plasticity modulates the cast value, it never gates applicability (K1).
186
+ * @param opts.noiseScale max exploration noise at p=1 (default 4 — enough to flip a separated unit)
187
+ * @param opts.rng seedable RNG for reproducible exploration (default Math.random)
188
+ * @returns { Net: { infer } }
189
+ *
190
+ * Concept wiring: { require:['input'], provider:['Net::infer'], net:{ inputKeys:['x0','x1'], as:'' } }
191
+ * Emits the self-flag + <as>pred (a scalar for a single output, else the vector).
192
+ */
193
+ function createNet( net, opts ) {
194
+ opts = opts || {};
195
+ var plasticity = opts.plasticity,
196
+ noiseScale = opts.noiseScale == null ? 4 : opts.noiseScale,
197
+ rng = opts.rng || Math.random;
198
+ return {
199
+ Net: {
200
+ infer: function ( graph, concept, scope, argz, cb ) {
201
+ var cfg = Object.assign({ inputKeys: opts.inputKeys || [], as: '' },
202
+ concept._schema && concept._schema.net, argz && argz[0]),
203
+ x = cfg.inputKeys.map(function ( k ) { return Number(scope._[k]); }),
204
+ p = plasticity ? plasticity(concept._name) : 0,
205
+ noise = noiseScale * p,
206
+ y = noise > 0 ? predictNoisy(net, x, { noise: noise, rng: rng }) : predictHard(net, x),
207
+ facts = { $_id: '_parent' };
208
+ facts[concept._name] = true;
209
+ facts[cfg.as + 'pred'] = y.length === 1 ? y[0] : y;
210
+ cb(null, facts);
211
+ }
212
+ }
213
+ };
214
+ }
215
+
216
+ /** A ready-made inference concept fragment for a baked net. */
217
+ function netConceptTree( opts ) {
218
+ opts = opts || {};
219
+ return {
220
+ childConcepts: {
221
+ Net: {
222
+ _id: 'Net', _name: 'Net',
223
+ require: [opts.require || 'input'],
224
+ provider: ['Net::infer'],
225
+ net: { inputKeys: opts.inputKeys || [], as: opts.as || '' }
226
+ }
227
+ }
228
+ };
229
+ }
230
+
231
+ module.exports = {
232
+ trainNet: trainNet,
233
+ predictHard: predictHard,
234
+ predictNoisy: predictNoisy,
235
+ forward: forward,
236
+ mseLoss: mseLoss,
237
+ createNet: createNet,
238
+ netConceptTree: netConceptTree
239
+ };