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.
- package/CONTRIBUTING.md +22 -0
- package/LICENSE +661 -0
- package/README.md +245 -0
- package/bin/sg +20 -0
- package/concepts/README.md +23 -0
- package/concepts/_substrate/Claim/Confidence/Trusted.json +6 -0
- package/concepts/_substrate/Claim/Confidence.json +10 -0
- package/concepts/_substrate/Claim/Freshness/Stale.json +7 -0
- package/concepts/_substrate/Claim/Freshness.json +5 -0
- package/concepts/_substrate/Claim/Verification/Refuted.json +6 -0
- package/concepts/_substrate/Claim/Verification.json +6 -0
- package/concepts/_substrate/Claim.json +4 -0
- package/concepts/_substrate/Frontier/InBeam.json +6 -0
- package/concepts/_substrate/Frontier/Stuck.json +6 -0
- package/concepts/_substrate/Frontier.json +5 -0
- package/concepts/_substrate/Intake/ToTask.json +22 -0
- package/concepts/_substrate/Intake/Typed.json +10 -0
- package/concepts/_substrate/Intake.json +27 -0
- package/concepts/_substrate/Task/Answer.json +12 -0
- package/concepts/_substrate/Task/Complexity/Atomic.json +5 -0
- package/concepts/_substrate/Task/Complexity/Compound/Expansion.json +7 -0
- package/concepts/_substrate/Task/Complexity/Compound.json +6 -0
- package/concepts/_substrate/Task/Complexity.json +6 -0
- package/concepts/_substrate/Task/EvalComplexity.json +13 -0
- package/concepts/_substrate/Task/ReportUp.json +6 -0
- package/concepts/_substrate/Task/Rollup.json +10 -0
- package/concepts/_substrate/Task.json +5 -0
- package/concepts/clinical/Diagnosis/Medication.json +6 -0
- package/concepts/clinical/Diagnosis.json +15 -0
- package/concepts/clinical/Observation/LabValue/OutOfRange.json +6 -0
- package/concepts/clinical/Observation/LabValue.json +5 -0
- package/concepts/clinical/Observation.json +6 -0
- package/concepts/common/Document/pathBasket.json +6 -0
- package/concepts/common/Document.json +3 -0
- package/concepts/common/Edge/Distance.json +13 -0
- package/concepts/common/Edge/Stay/LongStay.json +8 -0
- package/concepts/common/Edge/Stay.json +8 -0
- package/concepts/common/Edge/Travel/LongTravel.json +16 -0
- package/concepts/common/Edge/Travel/ShortTravel.json +11 -0
- package/concepts/common/Edge/Travel/targetNode.json +7 -0
- package/concepts/common/Edge/Travel.json +7 -0
- package/concepts/common/Edge.json +3 -0
- package/concepts/common/Vertice.json +3 -0
- package/concepts/supply/Fulfillment.json +10 -0
- package/concepts/supply/Inventory/Reorder.json +7 -0
- package/concepts/supply/Inventory.json +5 -0
- package/concepts/supply/Procurement/SupplierConfirm.json +6 -0
- package/concepts/supply/Procurement.json +8 -0
- package/concepts/supply/Transport.json +5 -0
- package/doc/API.md +556 -0
- package/doc/CAPABILITIES.md +373 -0
- package/doc/MODELISATION.md +511 -0
- package/doc/architecture.md +267 -0
- package/doc/concept-as-graph.md +379 -0
- package/doc/concept-learning.md +168 -0
- package/doc/creative-loop-map.md +202 -0
- package/doc/original-2016-doc.md +148 -0
- package/doc/usage.md +501 -0
- package/lib/authoring/abstract.js +401 -0
- package/lib/authoring/abstraction.js +190 -0
- package/lib/authoring/adapt.js +393 -0
- package/lib/authoring/ancestry.js +216 -0
- package/lib/authoring/author.js +147 -0
- package/lib/authoring/bounded-merge.js +57 -0
- package/lib/authoring/canon.js +158 -0
- package/lib/authoring/clock.js +123 -0
- package/lib/authoring/combinator.js +92 -0
- package/lib/authoring/compete.js +76 -0
- package/lib/authoring/compose-hotspot.js +314 -0
- package/lib/authoring/compress.js +279 -0
- package/lib/authoring/concept-net.js +254 -0
- package/lib/authoring/concepts.js +66 -0
- package/lib/authoring/context-project.js +277 -0
- package/lib/authoring/contract.js +476 -0
- package/lib/authoring/corpus-pack.js +125 -0
- package/lib/authoring/cost-probe.js +86 -0
- package/lib/authoring/crystallize.js +555 -0
- package/lib/authoring/dag-decompose.js +256 -0
- package/lib/authoring/dataset-adapter.js +253 -0
- package/lib/authoring/debug-provider.js +92 -0
- package/lib/authoring/decompose.js +343 -0
- package/lib/authoring/emittability.js +236 -0
- package/lib/authoring/equilibrium.js +160 -0
- package/lib/authoring/extract.js +153 -0
- package/lib/authoring/forest.js +55 -0
- package/lib/authoring/forge-fallback.js +116 -0
- package/lib/authoring/givens.js +121 -0
- package/lib/authoring/glossary.js +177 -0
- package/lib/authoring/grammar-graph.js +191 -0
- package/lib/authoring/granularity.js +80 -0
- package/lib/authoring/graph-net.js +108 -0
- package/lib/authoring/ground.js +148 -0
- package/lib/authoring/higher-order.js +85 -0
- package/lib/authoring/hotspot.js +132 -0
- package/lib/authoring/hysteresis.js +85 -0
- package/lib/authoring/index.js +106 -0
- package/lib/authoring/lattice-morphism.js +61 -0
- package/lib/authoring/lattice-pack.js +148 -0
- package/lib/authoring/leaf-io.js +77 -0
- package/lib/authoring/library.js +156 -0
- package/lib/authoring/lifecycle.js +92 -0
- package/lib/authoring/loop.js +193 -0
- package/lib/authoring/master-loop.js +132 -0
- package/lib/authoring/mdl.js +209 -0
- package/lib/authoring/memo-stability.js +122 -0
- package/lib/authoring/method-explorer.js +138 -0
- package/lib/authoring/method-pack.js +181 -0
- package/lib/authoring/method.js +202 -0
- package/lib/authoring/mine.js +510 -0
- package/lib/authoring/mount.js +119 -0
- package/lib/authoring/negotiate.js +100 -0
- package/lib/authoring/parametric.js +139 -0
- package/lib/authoring/reaggregate.js +75 -0
- package/lib/authoring/rebalance.js +157 -0
- package/lib/authoring/recall.js +135 -0
- package/lib/authoring/registry.js +350 -0
- package/lib/authoring/relearn.js +147 -0
- package/lib/authoring/retention.js +89 -0
- package/lib/authoring/segment-proxy.js +114 -0
- package/lib/authoring/serve-leaf.js +64 -0
- package/lib/authoring/slot-aware-serve.js +77 -0
- package/lib/authoring/sound-invoke.js +59 -0
- package/lib/authoring/split-serve.js +74 -0
- package/lib/authoring/ste.js +239 -0
- package/lib/authoring/stock.js +101 -0
- package/lib/authoring/store.js +94 -0
- package/lib/authoring/supervise.js +138 -0
- package/lib/authoring/support.js +131 -0
- package/lib/authoring/task-mirror.js +51 -0
- package/lib/authoring/typed-loop.js +160 -0
- package/lib/authoring/validate.js +569 -0
- package/lib/authoring/widen.js +88 -0
- package/lib/combos/appliance.js +189 -0
- package/lib/combos/critique.js +377 -0
- package/lib/combos/defaults.js +86 -0
- package/lib/combos/durable-runner.js +115 -0
- package/lib/combos/forge.js +209 -0
- package/lib/combos/index.js +86 -0
- package/lib/combos/learning-library.js +253 -0
- package/lib/combos/mixture-serve.js +193 -0
- package/lib/combos/plan-loop.js +192 -0
- package/lib/combos/proxy-cache.js +272 -0
- package/lib/combos/self-mod.js +94 -0
- package/lib/durable/audit.js +102 -0
- package/lib/durable/checkpoint-store.js +550 -0
- package/lib/durable/fold.js +62 -0
- package/lib/durable/interpreter.js +242 -0
- package/lib/durable/xlate.js +185 -0
- package/lib/graph/Graph.js +2486 -0
- package/lib/graph/expr.js +307 -0
- package/lib/graph/index.js +26 -0
- package/lib/graph/log.js +134 -0
- package/lib/graph/objects/Concept.js +382 -0
- package/lib/graph/objects/Entity.js +503 -0
- package/lib/graph/objects/Node.js +57 -0
- package/lib/graph/objects/PathMap.js +557 -0
- package/lib/graph/objects/Segment.js +100 -0
- package/lib/graph/tasks/stabilize.js +59 -0
- package/lib/graph/tasks/taskflow.js +247 -0
- package/lib/index.js +122 -0
- package/lib/load.js +132 -0
- package/lib/providers/backends.js +117 -0
- package/lib/providers/borderline.js +129 -0
- package/lib/providers/cache.js +226 -0
- package/lib/providers/canonicalize.js +198 -0
- package/lib/providers/constat.js +107 -0
- package/lib/providers/geo.js +53 -0
- package/lib/providers/index.js +116 -0
- package/lib/providers/intake.js +263 -0
- package/lib/providers/llm-local.js +88 -0
- package/lib/providers/llm.js +236 -0
- package/lib/providers/local-host.js +197 -0
- package/lib/providers/merge-consistency.js +162 -0
- package/lib/providers/nogood.js +149 -0
- package/lib/providers/reason-loop.js +223 -0
- package/lib/providers/semiring.js +285 -0
- package/lib/providers/solver-fork.js +163 -0
- package/lib/providers/stats.js +228 -0
- package/lib/providers/verify.js +120 -0
- package/lib/runtime/index.js +135 -0
- package/lib/runtime/invoke-pool.js +92 -0
- package/lib/runtime/protocol.js +210 -0
- package/lib/runtime/transport-socket.js +77 -0
- package/lib/runtime/worker-entry.js +31 -0
- package/lib/sg/cli.js +601 -0
- package/lib/sg/log-sinks.js +214 -0
- package/lib/sg/mcp.js +395 -0
- package/lib/sg/proxy-run.js +66 -0
- package/lib/sg/serve-demo.js +185 -0
- package/lib/sg/serve.js +205 -0
- package/lib/sg/trace.js +118 -0
- package/lib/studio/protocol.js +45 -0
- package/lib/studio/public/app.js +310 -0
- package/lib/studio/public/components/ConceptEditor.js +43 -0
- package/lib/studio/public/components/ConceptTree.js +43 -0
- package/lib/studio/public/components/CorpusPanel.js +68 -0
- package/lib/studio/public/components/ForkTree.js +36 -0
- package/lib/studio/public/components/GrammarGraph.js +99 -0
- package/lib/studio/public/components/GraphCanvas.js +156 -0
- package/lib/studio/public/components/Inspector.js +48 -0
- package/lib/studio/public/components/LearningPanel.js +85 -0
- package/lib/studio/public/components/PromptConsole.js +46 -0
- package/lib/studio/public/components/ProviderTrace.js +39 -0
- package/lib/studio/public/components/SessionSplit.js +47 -0
- package/lib/studio/public/components/TilingOverlay.js +40 -0
- package/lib/studio/public/components/Timeline.js +57 -0
- package/lib/studio/public/index.html +26 -0
- package/lib/studio/public/styles.css +185 -0
- package/lib/studio/public/ws.js +58 -0
- package/lib/studio/server.js +130 -0
- package/lib/studio/session.js +397 -0
- package/lib/studio/studio.js +149 -0
- 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
|
+
};
|