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,163 @@
|
|
|
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
|
+
* C-regime solver-fork — search-where-D-propagates, with a snapped frontier (the
|
|
21
|
+
* Mixture-of-Reasoners "C regime"; doc/WIP/HANDOFF.md §7 Tier 1; SOTA subgraph-grammars
|
|
22
|
+
* brick ii; experiment E8). Zero core change.
|
|
23
|
+
*
|
|
24
|
+
* The deterministic D socle PROPAGATES to a fixpoint; it cannot SEARCH. A regime-C
|
|
25
|
+
* sub-graph delegates a constraint problem to a backtracking solver (Logic-LM style,
|
|
26
|
+
* Pan 2023) and EXPORTS ONLY the snapped model across the merge frontier — the colour/
|
|
27
|
+
* assignment enums + a `sat` bool — never the search internals (step counts, the search
|
|
28
|
+
* tree, continuous scores). So the parent stays deterministic and auditable, the barrier
|
|
29
|
+
* holds, and UNSAT is a discrete outcome the D grammar can gate on.
|
|
30
|
+
*
|
|
31
|
+
* const { createSolver, snappedFrontier, solverConceptTree } = require('./solver-fork');
|
|
32
|
+
* register(Graph, [ createSolver({ solve: myZ3Backend }) ]); // wires Solve::run
|
|
33
|
+
* const child = parent.fork(spec, { conceptMap: { common: solverConceptTree() } });
|
|
34
|
+
* await nextStable(child);
|
|
35
|
+
* parent.merge(child, 'prob', snappedFrontier({ targetId: 'prob' })); // only sat+model cross
|
|
36
|
+
*
|
|
37
|
+
* The solver backend is INJECTED (backend-agnostic, like createLLMProvider's `ask`): a
|
|
38
|
+
* `solve(spec) -> { sat, model, ...internals }` function. The packaged default is a
|
|
39
|
+
* dependency-free backtracking finite-domain CSP (`backtrackColoring`) so the regime is
|
|
40
|
+
* runnable out of the box; the architectural point — search, snapped frontier, auditable
|
|
41
|
+
* D parent — is independent of hand-rolled-vs-Z3.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// ---- reference C-regime backend: a dependency-free backtracking CSP (graph k-colouring) ----
|
|
45
|
+
// spec = { nodes:[...], edges:[[u,v],...], colors?:[...] } -> { sat, model, steps }.
|
|
46
|
+
// model = a node->colour assignment (the snapped enum the frontier carries); steps is a
|
|
47
|
+
// search-internal cost (stays in the fork — it must NOT cross the frontier).
|
|
48
|
+
function backtrackColoring( spec ) {
|
|
49
|
+
spec = spec || {};
|
|
50
|
+
var nodes = spec.nodes || [];
|
|
51
|
+
var edges = spec.edges || [];
|
|
52
|
+
var colors = spec.colors || ['R', 'G', 'B'];
|
|
53
|
+
var assign = {}, adj = {};
|
|
54
|
+
nodes.forEach(function ( n ) { adj[n] = []; });
|
|
55
|
+
edges.forEach(function ( e ) { adj[e[0]].push(e[1]); adj[e[1]].push(e[0]); });
|
|
56
|
+
var steps = 0;
|
|
57
|
+
function bt( i ) {
|
|
58
|
+
if ( i === nodes.length ) return true;
|
|
59
|
+
var n = nodes[i];
|
|
60
|
+
for ( var ci = 0; ci < colors.length; ci++ ) {
|
|
61
|
+
var c = colors[ci];
|
|
62
|
+
steps++;
|
|
63
|
+
if ( adj[n].every(function ( m ) { return assign[m] !== c; }) ) {
|
|
64
|
+
assign[n] = c;
|
|
65
|
+
if ( bt(i + 1) ) return true;
|
|
66
|
+
delete assign[n];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
var sat = bt(0);
|
|
72
|
+
return { sat: sat, model: sat ? Object.assign({}, assign) : null, steps: steps };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build the C-regime solver-fork provider fragment (host opt-in, like createVerifier).
|
|
77
|
+
* @param opts.solve injected backend `(spec) -> { sat, model, ...internals }`
|
|
78
|
+
* (default: backtrackColoring).
|
|
79
|
+
* @returns { Solve: { run } }
|
|
80
|
+
*
|
|
81
|
+
* Concept wiring (in the C-fork grammar — see solverConceptTree):
|
|
82
|
+
* { require:['toSolve'], provider:['Solve::run'], solve:{ specKey:'spec', as:'' } }
|
|
83
|
+
* `specKey` names the scope fact holding the problem spec (omit -> the whole scope fact
|
|
84
|
+
* bag is the spec). Emits the self-flag + <as>sat (bool) + <as>model (the snapped
|
|
85
|
+
* assignment, null when UNSAT) + any solver internals the backend returns (e.g. `steps`)
|
|
86
|
+
* — internals are kept on the CHILD and dropped by snappedFrontier at the merge.
|
|
87
|
+
*/
|
|
88
|
+
function createSolver( opts ) {
|
|
89
|
+
opts = opts || {};
|
|
90
|
+
var solve = opts.solve || backtrackColoring;
|
|
91
|
+
return {
|
|
92
|
+
Solve: {
|
|
93
|
+
run: function ( graph, concept, scope, argz, cb ) {
|
|
94
|
+
var cfg = Object.assign({ as: '' }, concept._schema && concept._schema.solve, argz && argz[0]),
|
|
95
|
+
spec = cfg.specKey ? graph.getRef(cfg.specKey, scope) : scope._,
|
|
96
|
+
r = solve(spec) || {},
|
|
97
|
+
as = cfg.as || '',
|
|
98
|
+
facts = { $_id: '_parent' };
|
|
99
|
+
facts[concept._name] = true;
|
|
100
|
+
facts[as + 'sat'] = !!r.sat;
|
|
101
|
+
facts[as + 'model'] = r.sat ? (r.model != null ? r.model : null) : null;
|
|
102
|
+
// solver internals (steps, the search tree, …) are emitted on the child only;
|
|
103
|
+
// they document the search but must NOT cross the frontier (see snappedFrontier).
|
|
104
|
+
for ( var k in r ) if ( k !== 'sat' && k !== 'model' ) facts[as + k] = r[k];
|
|
105
|
+
cb(null, facts);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A merge-projection that crosses ONLY the snapped model across the frontier — the C-regime
|
|
113
|
+
* barrier: the colour/assignment enums + the `sat` bool cross; search internals (step counts,
|
|
114
|
+
* the search tree, continuous scores) stay in the fork. Pass the returned function as the
|
|
115
|
+
* 3rd arg to `parent.merge(child, targetId, project)`.
|
|
116
|
+
* @param opts.targetId the parent object id to write onto (required)
|
|
117
|
+
* @param opts.sourceId the child object holding the solved facts (default = targetId)
|
|
118
|
+
* @param opts.frontier keys allowed to cross (default ['sat','model'])
|
|
119
|
+
* @param opts.as the child's output prefix (default '')
|
|
120
|
+
*/
|
|
121
|
+
function snappedFrontier( opts ) {
|
|
122
|
+
opts = opts || {};
|
|
123
|
+
var targetId = opts.targetId,
|
|
124
|
+
sourceId = opts.sourceId || targetId,
|
|
125
|
+
frontier = opts.frontier || ['sat', 'model'],
|
|
126
|
+
as = opts.as || '';
|
|
127
|
+
return function ( child ) {
|
|
128
|
+
var f = child.getEtty(sourceId)._,
|
|
129
|
+
tpl = { $$_id: targetId };
|
|
130
|
+
frontier.forEach(function ( k ) { tpl[k] = f[as + k]; }); // ONLY these keys cross
|
|
131
|
+
return tpl;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The C-fork grammar fragment: a `Solve` concept whose provider SEARCHES. Use it as the
|
|
137
|
+
* forked sub-graph's conceptMap so the fork runs the C regime.
|
|
138
|
+
* @param opts.require the trigger fact that marks an object as "to solve" (default 'toSolve')
|
|
139
|
+
* @param opts.specKey scope fact holding the spec (passed through to the provider)
|
|
140
|
+
* @param opts.as output-fact prefix (default '')
|
|
141
|
+
*/
|
|
142
|
+
function solverConceptTree( opts ) {
|
|
143
|
+
opts = opts || {};
|
|
144
|
+
var solve = { as: opts.as || '' };
|
|
145
|
+
if ( opts.specKey ) solve.specKey = opts.specKey;
|
|
146
|
+
return {
|
|
147
|
+
childConcepts: {
|
|
148
|
+
Solve: {
|
|
149
|
+
_id: 'Solve', _name: 'Solve',
|
|
150
|
+
require: [opts.require || 'toSolve'],
|
|
151
|
+
provider: ['Solve::run'],
|
|
152
|
+
solve: solve
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
backtrackColoring: backtrackColoring,
|
|
160
|
+
createSolver: createSolver,
|
|
161
|
+
snappedFrontier: snappedFrontier,
|
|
162
|
+
solverConceptTree: solverConceptTree
|
|
163
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
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
|
+
* L1 graded-evidence statistics — hierarchical Beta-Binomial shrinkage along the IS-A
|
|
21
|
+
* backbone (the bounded probabilistic layer; doc/WIP/HANDOFF.md §7 Tier 2; experiment A).
|
|
22
|
+
* Zero core change, and ADDITIVE: the deterministic socle is untouched — this only writes
|
|
23
|
+
* SNAPPED reliability facts that a downstream `ensure` gates on (graded evidence as a
|
|
24
|
+
* discrete fact + a threshold, the L1 frame, never a raw float on a defeasant gate).
|
|
25
|
+
*
|
|
26
|
+
* The statistical point (A3): at small n a per-leaf success rate is high-variance; shrinking
|
|
27
|
+
* it toward the parent "grand mean" prior (θ̂ = (n·x̄ + κ·prior)/(n+κ)) cuts MSE ≈7× at n=2,
|
|
28
|
+
* with the advantage decaying as n grows. The barrier point (A2): the leaf consumes the prior
|
|
29
|
+
* SNAPPED to its own grain (the bucket midpoint), and the downstream gate keys on the snapped
|
|
30
|
+
* reliability RANK — so sub-bucket noise doesn't churn the memo (snapping costs only ~1–4% MSE).
|
|
31
|
+
*
|
|
32
|
+
* const { createStats, shrinkageConceptTree } = require('./stats');
|
|
33
|
+
* register(Graph, [ createStats() ]); // wires Stats::report / Stats::grandMean / Stats::shrink
|
|
34
|
+
* // pool the children's grand mean, then (a host wave later, once the prior exists)
|
|
35
|
+
* // shrink each leaf toward the SNAPPED prior and gate Trusted on the snapped rank.
|
|
36
|
+
*
|
|
37
|
+
* Hierarchical ordering: the grand-mean prior must exist before a leaf can shrink toward it.
|
|
38
|
+
* That is the "offline between revisions" pattern — pool first (its own settle), then add the
|
|
39
|
+
* shrink wave — not a live two-level reactive pool (which would need cross-object aggregation,
|
|
40
|
+
* roadmap #8). The pool reduces a race-free {__push} obs array gated on its cardinality.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// ---- reliability bands: snap a probability to a discrete rank + the band MIDPOINT (the
|
|
44
|
+
// snapped prior the synthesis mandates). 4 bands, edges 0.5/0.75/0.9. Override via opts.bands. ----
|
|
45
|
+
var RELIABILITY_BANDS = [
|
|
46
|
+
{ max: 0.5, label: 'low', rank: 0, mid: 0.25 },
|
|
47
|
+
{ max: 0.75, label: 'med', rank: 1, mid: 0.625 },
|
|
48
|
+
{ max: 0.9, label: 'high', rank: 2, mid: 0.825 },
|
|
49
|
+
{ label: 'certain', rank: 3, mid: 0.95 }
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
function bandOf( p, bands ) {
|
|
53
|
+
bands = bands || RELIABILITY_BANDS;
|
|
54
|
+
for ( var i = 0; i < bands.length; i++ )
|
|
55
|
+
if ( bands[i].max == null || p < bands[i].max ) return { rank: bands[i].rank, label: bands[i].label, mid: bands[i].mid };
|
|
56
|
+
var last = bands[bands.length - 1];
|
|
57
|
+
return { rank: last.rank, label: last.label, mid: last.mid };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Beta-Binomial shrinkage of a leaf rate toward a prior.
|
|
62
|
+
* θ̂ = (n·x̄ + κ·prior) / (n + κ)
|
|
63
|
+
* @param o.succ leaf successes
|
|
64
|
+
* @param o.tot leaf trials (n)
|
|
65
|
+
* @param o.prior the (snapped) parent prior in [0,1]
|
|
66
|
+
* @param o.kappa shrinkage strength (pseudo-count); larger -> more pooling
|
|
67
|
+
* @returns { theta, raw, n } raw = the un-shrunk x̄ (0.5 when n=0)
|
|
68
|
+
*/
|
|
69
|
+
function shrink( o ) {
|
|
70
|
+
o = o || {};
|
|
71
|
+
var n = Number(o.tot) || 0,
|
|
72
|
+
succ = Number(o.succ) || 0,
|
|
73
|
+
prior = o.prior == null ? 0.5 : Number(o.prior),
|
|
74
|
+
kappa = o.kappa == null ? 8 : Number(o.kappa),
|
|
75
|
+
raw = n ? succ / n : 0.5,
|
|
76
|
+
theta = (n * raw + kappa * prior) / (n + kappa);
|
|
77
|
+
return { theta: theta, raw: raw, n: n };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Empirical-Bayes shrinkage strength κ from a set of per-leaf raw rates at trial size n:
|
|
82
|
+
* κ ≈ n · (within-leaf sampling variance) / (between-leaf signal variance). Large when the
|
|
83
|
+
* leaves barely differ beyond sampling noise (pool hard); small when they genuinely spread.
|
|
84
|
+
* @param rawRates array of per-leaf x̄ in [0,1]
|
|
85
|
+
* @param n trials per leaf
|
|
86
|
+
*/
|
|
87
|
+
function empiricalBayesKappa( rawRates, n ) {
|
|
88
|
+
var M = (rawRates || []).length;
|
|
89
|
+
if ( !M || !n ) return 0;
|
|
90
|
+
var gm = rawRates.reduce(function ( a, b ) { return a + b; }, 0) / M,
|
|
91
|
+
varRaw = rawRates.reduce(function ( a, b ) { return a + (b - gm) * (b - gm); }, 0) / M,
|
|
92
|
+
sampVar = gm * (1 - gm) / Math.max(1, n),
|
|
93
|
+
betweenVar = Math.max(1e-4, varRaw - sampVar);
|
|
94
|
+
return sampVar / betweenVar * n;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build the L1 statistics provider fragment (host opt-in, like createVerifier).
|
|
99
|
+
* @param opts.bands default reliability bands (per-concept override via the concept's `stats.bands`)
|
|
100
|
+
* @param opts.kappa default shrinkage strength (default 8)
|
|
101
|
+
* @returns { Stats: { report, grandMean, shrink } }
|
|
102
|
+
*
|
|
103
|
+
* Concept wiring:
|
|
104
|
+
* Stats::report { require:['Cat'], provider:['Stats::report'], stats:{ poolId:'pool', succKey:'succ', totKey:'tot' } }
|
|
105
|
+
* Stats::grandMean { require:['PoolRoot'], ensure:['$obs.length==$expected'], provider:['Stats::grandMean'] }
|
|
106
|
+
* Stats::shrink { require:['Cat','Report'], provider:['Stats::shrink'], stats:{ poolId:'pool', priorKey:'pHat0Bucket', kappa:8 } }
|
|
107
|
+
* shrink emits <as>thetaHat / <as>rawHat / <as>relRank / <as>relBucket / <as>priorMid (gate on relRank).
|
|
108
|
+
*/
|
|
109
|
+
function createStats( opts ) {
|
|
110
|
+
opts = opts || {};
|
|
111
|
+
var defaultBands = opts.bands,
|
|
112
|
+
defaultKappa = opts.kappa == null ? 8 : opts.kappa;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
Stats: {
|
|
116
|
+
// each leaf appends its (succ,tot) into the pool's obs array (race-free {__push}).
|
|
117
|
+
report: function ( graph, concept, scope, argz, cb ) {
|
|
118
|
+
var cfg = Object.assign({ poolId: 'pool', succKey: 'succ', totKey: 'tot' }, concept._schema && concept._schema.stats, argz && argz[0]),
|
|
119
|
+
push = { __push: { succ: scope._[cfg.succKey], tot: scope._[cfg.totKey] } },
|
|
120
|
+
poolWrite = { $$_id: cfg.poolId },
|
|
121
|
+
self = { $_id: '_parent' };
|
|
122
|
+
self[concept._name] = true;
|
|
123
|
+
poolWrite[cfg.obsKey || 'obs'] = push;
|
|
124
|
+
cb(null, [self, poolWrite]);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// reduce the pooled obs -> the grand mean, SNAPPED to its band (the prior the leaves consume).
|
|
128
|
+
grandMean: function ( graph, concept, scope, argz, cb ) {
|
|
129
|
+
var cfg = Object.assign({ as: '' }, concept._schema && concept._schema.stats, argz && argz[0]),
|
|
130
|
+
obs = scope._[cfg.obsKey || 'obs'] || [],
|
|
131
|
+
s = 0, t = 0;
|
|
132
|
+
for ( var i = 0; i < obs.length; i++ ) { s += obs[i].succ; t += obs[i].tot; }
|
|
133
|
+
var pHat0 = t ? s / t : 0.5,
|
|
134
|
+
b = bandOf(pHat0, cfg.bands || defaultBands),
|
|
135
|
+
as = cfg.as || '',
|
|
136
|
+
facts = { $_id: '_parent' };
|
|
137
|
+
facts[concept._name] = true;
|
|
138
|
+
facts[as + 'pHat0'] = pHat0;
|
|
139
|
+
facts[as + 'pHat0Rank'] = b.rank;
|
|
140
|
+
facts[as + 'pHat0Bucket'] = b.label;
|
|
141
|
+
facts[as + 'pHat0Mid'] = b.mid; // the snapped prior midpoint
|
|
142
|
+
facts[as + 'pooledSucc'] = s;
|
|
143
|
+
facts[as + 'pooledTot'] = t;
|
|
144
|
+
cb(null, facts);
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// shrink the leaf toward the parent's SNAPPED prior; snap θ̂ to a reliability rank.
|
|
148
|
+
shrink: function ( graph, concept, scope, argz, cb ) {
|
|
149
|
+
var cfg = Object.assign({ poolId: 'pool', priorKey: 'pHat0Bucket', succKey: 'succ', totKey: 'tot', as: '' },
|
|
150
|
+
concept._schema && concept._schema.stats, argz && argz[0]),
|
|
151
|
+
bands = cfg.bands || defaultBands,
|
|
152
|
+
pool = graph.getEtty(cfg.poolId)._,
|
|
153
|
+
priorMid, r;
|
|
154
|
+
// consume the SNAPPED prior (the band midpoint) — barrier-clean, no continuous leak.
|
|
155
|
+
// Prefer an explicit midpoint fact (grandMean writes pHat0Mid); else resolve it from
|
|
156
|
+
// the pool's snapped band label.
|
|
157
|
+
if ( cfg.priorMidKey ) {
|
|
158
|
+
priorMid = pool[cfg.priorMidKey];
|
|
159
|
+
} else {
|
|
160
|
+
var label = pool[cfg.priorKey],
|
|
161
|
+
band = (bands || RELIABILITY_BANDS).filter(function ( x ) { return x.label === label; })[0];
|
|
162
|
+
priorMid = band ? band.mid : 0.5;
|
|
163
|
+
}
|
|
164
|
+
r = shrink({ succ: scope._[cfg.succKey], tot: scope._[cfg.totKey], prior: priorMid, kappa: cfg.kappa == null ? defaultKappa : cfg.kappa });
|
|
165
|
+
var b = bandOf(r.theta, bands),
|
|
166
|
+
as = cfg.as || '',
|
|
167
|
+
facts = { $_id: '_parent' };
|
|
168
|
+
facts[concept._name] = true;
|
|
169
|
+
facts[as + 'thetaHat'] = r.theta;
|
|
170
|
+
facts[as + 'rawHat'] = r.raw;
|
|
171
|
+
facts[as + 'relRank'] = b.rank;
|
|
172
|
+
facts[as + 'relBucket'] = b.label;
|
|
173
|
+
facts[as + 'priorMid'] = priorMid;
|
|
174
|
+
cb(null, facts);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A ready-made hierarchical-shrinkage fragment. Returns the two waves separately because the
|
|
182
|
+
* prior must exist before the leaves shrink (the offline-between-revisions ordering):
|
|
183
|
+
* .pool — the PoolRoot/Pool (grandMean) + Cat/Report concepts (wave 1, boot it);
|
|
184
|
+
* .shrink — the Reliability (shrink) + Trusted gate concept (wave 2, addConcept once pooled).
|
|
185
|
+
* @param opts.kappa shrinkage strength
|
|
186
|
+
* @param opts.trustedRank the Trusted gate threshold on the snapped rank (default 2 = high)
|
|
187
|
+
* @param opts.poolId the pool object id (default 'pool')
|
|
188
|
+
*/
|
|
189
|
+
function shrinkageConceptTree( opts ) {
|
|
190
|
+
opts = opts || {};
|
|
191
|
+
var poolId = opts.poolId || 'pool',
|
|
192
|
+
trustedRank = opts.trustedRank == null ? 2 : opts.trustedRank,
|
|
193
|
+
stats = { poolId: poolId };
|
|
194
|
+
if ( opts.kappa != null ) stats.kappa = opts.kappa;
|
|
195
|
+
return {
|
|
196
|
+
pool: {
|
|
197
|
+
childConcepts: {
|
|
198
|
+
PoolRoot: {
|
|
199
|
+
_id: 'PoolRoot', _name: 'PoolRoot', require: ['PoolRoot'],
|
|
200
|
+
childConcepts: {
|
|
201
|
+
Pool: { _id: 'Pool', _name: 'Pool', require: ['PoolRoot'], ensure: ['$obs.length==$expected'], provider: ['Stats::grandMean'] }
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
Cat: {
|
|
205
|
+
_id: 'Cat', _name: 'Cat', require: ['Cat'],
|
|
206
|
+
childConcepts: {
|
|
207
|
+
Report: { _id: 'Report', _name: 'Report', require: ['Cat'], provider: ['Stats::report'], stats: { poolId: poolId } }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
shrink: {
|
|
213
|
+
_id: 'Reliability', _name: 'Reliability', require: ['Cat', 'Report'], provider: ['Stats::shrink'], stats: stats,
|
|
214
|
+
childConcepts: {
|
|
215
|
+
Trusted: { _id: 'Trusted', _name: 'Trusted', require: ['Reliability'], ensure: ['$relRank>=' + trustedRank] }
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = {
|
|
222
|
+
RELIABILITY_BANDS: RELIABILITY_BANDS,
|
|
223
|
+
bandOf: bandOf,
|
|
224
|
+
shrink: shrink,
|
|
225
|
+
empiricalBayesKappa: empiricalBayesKappa,
|
|
226
|
+
createStats: createStats,
|
|
227
|
+
shrinkageConceptTree: shrinkageConceptTree
|
|
228
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
* Verification providers (roadmap #3) — the structural answer to K3 (coherence ≠ truth).
|
|
21
|
+
* The engine maintains COHERENCE, never TRUTH; verification makes unreliability *visible
|
|
22
|
+
* and non-propagating* by emitting discrete VERDICT facts that downstream concepts gate
|
|
23
|
+
* on via `ensure` (a refuted fact auto-retracts its dependents — refutation IS defeasance,
|
|
24
|
+
* no new engine path). Verdicts are discrete facts (the #1 typed-fact spine), never prose,
|
|
25
|
+
* and NEVER overwrite the checked fact (the graph is additive; experts don't fight).
|
|
26
|
+
*
|
|
27
|
+
* Three reliable patterns (all verified against the engine):
|
|
28
|
+
* 1. DETERMINISTIC verifier = a concept whose `ensure` IS the invariant (full `expr.js`
|
|
29
|
+
* grammar — uncapped). Its self-flag is the verdict; a target change re-tests it and
|
|
30
|
+
* auto-retracts. Consumers either nest under it (structural cascade) or `ensure` a
|
|
31
|
+
* verdict fact it writes. Prefer this — deterministic checkers >> LLM-refuters.
|
|
32
|
+
* 2. INDEPENDENT verdict provider (`Verify::check`) — for a check that must run as an
|
|
33
|
+
* effect (an external lookup, or an LLM-refuter): reads the target, runs a checker,
|
|
34
|
+
* writes a DISTINCT verdict fact + `VerifiedAgainst` provenance. Downstream gates via
|
|
35
|
+
* `ensure:["$xVerified == true"]`.
|
|
36
|
+
* 3. k-of-n VOTING (`Vote::tally`) — self-consistency: n strategies `{__push}` a vote
|
|
37
|
+
* into a grow-only array; a `Vote` concept gated `ensure:["$votes.length == $n"]`
|
|
38
|
+
* (the proven completion-gate) emits majority `consensus` + `confidence = agree/n`.
|
|
39
|
+
* Downstream gates `ensure:["$confidence >= threshold"]`.
|
|
40
|
+
*
|
|
41
|
+
* Independence discipline: a refuter must not be the same call that produced the fact
|
|
42
|
+
* (don't ask the hallucination to grade itself) — a deterministic checker, a different
|
|
43
|
+
* provider, or an adversarially-framed LLM. k-of-n over a biased model votes confidently
|
|
44
|
+
* wrong; treat `confidence` as a heuristic, never a proof.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// ---- deterministic checker library: (value, params) -> { pass, reason } ----
|
|
48
|
+
// Pure, side-effect-free, total (never throws). Add domain checks here as needed.
|
|
49
|
+
var checks = {
|
|
50
|
+
range : function ( v, p ) { var n = Number(v); return { pass: isFinite(n) && n >= p.min && n <= p.max, reason: 'range[' + p.min + ',' + p.max + ']' }; },
|
|
51
|
+
oneOf : function ( v, p ) { return { pass: (p.values || []).indexOf(v) !== -1, reason: 'oneOf' }; },
|
|
52
|
+
equals : function ( v, p ) { return { pass: v === p.to, reason: 'equals' }; },
|
|
53
|
+
approx : function ( v, p ) { var n = Number(v); return { pass: isFinite(n) && Math.abs(n - Number(p.to)) <= (p.tol || 0), reason: 'approx±' + (p.tol || 0) }; },
|
|
54
|
+
nonEmpty: function ( v ) { return { pass: v != null && (typeof v === 'string' || Array.isArray(v) ? v.length > 0 : true), reason: 'nonEmpty' }; }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ---- majority vote over an array of discrete votes ----
|
|
58
|
+
function majority( votes ) {
|
|
59
|
+
var counts = {}, best = null, bestN = 0;
|
|
60
|
+
for ( var i = 0; i < votes.length; i++ ) {
|
|
61
|
+
var k = JSON.stringify(votes[i]);
|
|
62
|
+
counts[k] = (counts[k] || 0) + 1;
|
|
63
|
+
if ( counts[k] > bestN ) { bestN = counts[k]; best = votes[i]; }
|
|
64
|
+
}
|
|
65
|
+
return { value: best, agree: bestN, total: votes.length, confidence: votes.length ? bestN / votes.length : 0 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build the verification provider-map fragment.
|
|
70
|
+
* @param opts.checks extra deterministic checkers merged over the defaults.
|
|
71
|
+
* @returns { Verify: { check }, Vote: { tally } }
|
|
72
|
+
*/
|
|
73
|
+
function createVerifier( opts ) {
|
|
74
|
+
opts = opts || {};
|
|
75
|
+
var lib = Object.assign({}, checks, opts.checks);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
// Verify::check — independent verdict provider. Concept wiring:
|
|
79
|
+
// { provider:['Verify::check'],
|
|
80
|
+
// verify: { target:'$x', check:'range', params:{min:0,max:100}, as:'x' } }
|
|
81
|
+
// Emits <as>Verdict / <as>Verified / <as>Reason / <as>VerifiedAgainst — never $x itself.
|
|
82
|
+
Verify: {
|
|
83
|
+
check: function ( graph, concept, scope, argz, cb ) {
|
|
84
|
+
var cfg = Object.assign({}, concept._schema && concept._schema.verify, argz && argz[0]),
|
|
85
|
+
prefix = cfg.as || concept._name,
|
|
86
|
+
checker = lib[cfg.check],
|
|
87
|
+
value = graph.getRef(cfg.target, scope),
|
|
88
|
+
res = checker ? checker(value, cfg.params || {}) : { pass: false, reason: 'unknown-check:' + cfg.check },
|
|
89
|
+
facts = { $_id: '_parent' };
|
|
90
|
+
facts[concept._name] = true;
|
|
91
|
+
facts[prefix + 'Verdict'] = res.pass ? 'pass' : 'fail';
|
|
92
|
+
facts[prefix + 'Verified'] = res.pass;
|
|
93
|
+
facts[prefix + 'Reason'] = res.reason;
|
|
94
|
+
facts[prefix + 'VerifiedAgainst'] = cfg.check;
|
|
95
|
+
cb(null, facts);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
// Vote::tally — k-of-n consensus. Concept wiring:
|
|
99
|
+
// { require:['votes'], ensure:['$votes.length == $expected'], provider:['Vote::tally'],
|
|
100
|
+
// vote: { votesKey:'votes', as:'' } }
|
|
101
|
+
// Emits <as>consensus / <as>confidence / <as>agree / <as>total (flat by default).
|
|
102
|
+
Vote: {
|
|
103
|
+
tally: function ( graph, concept, scope, argz, cb ) {
|
|
104
|
+
var cfg = Object.assign({}, concept._schema && concept._schema.vote, argz && argz[0]),
|
|
105
|
+
prefix = cfg.as || '',
|
|
106
|
+
votes = graph.getRef(cfg.votesKey || 'votes', scope) || [],
|
|
107
|
+
m = majority(votes),
|
|
108
|
+
facts = { $_id: '_parent' };
|
|
109
|
+
facts[concept._name] = true;
|
|
110
|
+
facts[prefix + 'consensus'] = m.value;
|
|
111
|
+
facts[prefix + 'confidence'] = m.confidence;
|
|
112
|
+
facts[prefix + 'agree'] = m.agree;
|
|
113
|
+
facts[prefix + 'total'] = m.total;
|
|
114
|
+
cb(null, facts);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { createVerifier: createVerifier, checks: checks, majority: majority };
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
* Parent-side sub-graph runtime — dispatch graph parts to a warm worker for horizontal scale. The worker
|
|
21
|
+
* rehydrates from a conceptMap (JSON) + provider dir path + a seed snapshot (JSON); the one effect that can't be
|
|
22
|
+
* shipped — a parent-bound model `ask` — is PROXIED back over the channel and the parent's real `ask` answers it.
|
|
23
|
+
*
|
|
24
|
+
* const { createGraphWorker } = require('skynet-graph/lib/runtime');
|
|
25
|
+
* const w = createGraphWorker({ conceptMap, geo: true }); // default: worker_threads (same machine)
|
|
26
|
+
* const snapshot = await w.dispatch(seed); // -> graph.serialize()
|
|
27
|
+
* w.terminate();
|
|
28
|
+
*
|
|
29
|
+
* TRANSPORT (P5). The {init, dispatch, ask, result, log} protocol is transport-agnostic (see protocol.js). Two
|
|
30
|
+
* transports slot in behind the SAME channel seam:
|
|
31
|
+
* • worker_threads (default) — same-machine parallelism, spawns worker-entry.js;
|
|
32
|
+
* • socket (opts.address / transport) — a CROSS-INSTANCE runtime: connect to a `serveGraphWorker` on another
|
|
33
|
+
* process or machine. `createGraphWorker({ address:{path|port}, … })`.
|
|
34
|
+
* A host stands up the remote side with `serveGraphWorker({ path } | { port })`.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} [opts.conceptMap] concept map (JSON) — or
|
|
37
|
+
* @param {string} [opts.concepts] a concept-set dir the worker can read
|
|
38
|
+
* @param {string} [opts.providers] provider dir/file the worker loads locally
|
|
39
|
+
* @param {boolean} [opts.geo] register the packaged Geo provider
|
|
40
|
+
* @param {boolean} [opts.llm] register the packaged LLM provider, wired to `opts.ask`
|
|
41
|
+
* @param {function} [opts.ask] parent model backend — answers proxied calls (prompt,opts)=>Promise
|
|
42
|
+
* @param {object} [opts.conf] data-only Graph cfg for dispatched graphs
|
|
43
|
+
* @param {object} [opts.logger] parent logger — worker log records re-emit into it (tagged {worker:true})
|
|
44
|
+
* @param {string} [opts.logLevel] threshold for the worker's forwarding logger (default 'verbose')
|
|
45
|
+
* @param {object} [opts.address] socket transport: { path } (unix) | { port[, host] } (TCP). Omit = threads.
|
|
46
|
+
* @param {string} [opts.transport] 'socket' | 'thread' (default). 'socket' requires opts.address.
|
|
47
|
+
* @returns {{ready, dispatch, terminate, worker?}}
|
|
48
|
+
*/
|
|
49
|
+
var path = require('path');
|
|
50
|
+
var protocol = require('./protocol.js');
|
|
51
|
+
|
|
52
|
+
function threadChannel() {
|
|
53
|
+
var Worker = require('worker_threads').Worker;
|
|
54
|
+
var worker = new Worker(path.join(__dirname, 'worker-entry.js'));
|
|
55
|
+
return {
|
|
56
|
+
worker: worker,
|
|
57
|
+
send: function ( m ) { worker.postMessage(m); },
|
|
58
|
+
onMessage: function ( fn ) { worker.on('message', fn); },
|
|
59
|
+
onError: function ( fn ) { worker.on('error', fn); },
|
|
60
|
+
close: function () { return worker.terminate(); }
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createGraphWorker( opts ) {
|
|
65
|
+
opts = opts || {};
|
|
66
|
+
|
|
67
|
+
// ── socket transport: connect to a remote serveGraphWorker; the API awaits the connection lazily ──────────
|
|
68
|
+
if ( opts.transport === 'socket' || opts.address ) {
|
|
69
|
+
if ( !opts.address ) throw new Error("createGraphWorker transport 'socket' needs opts.address ({ path } | { port })");
|
|
70
|
+
var connect = require('./transport-socket.js').connect;
|
|
71
|
+
var connected = connect(opts.address).then(function ( channel ) { return protocol.attachClient(channel, opts); });
|
|
72
|
+
return {
|
|
73
|
+
ready: function () { return connected.then(function ( a ) { return a.ready(); }); },
|
|
74
|
+
dispatch: function ( seed, dopts ) { return connected.then(function ( a ) { return a.dispatch(seed, dopts); }); },
|
|
75
|
+
invoke: function ( iopts ) { return connected.then(function ( a ) { return a.invoke(iopts); }); },
|
|
76
|
+
terminate: function () { return connected.then(function ( a ) { return a.terminate(); }); }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── default: worker_threads (same-machine) ────────────────────────────────────────────────────────────────
|
|
81
|
+
var channel = threadChannel();
|
|
82
|
+
var api = protocol.attachClient(channel, opts);
|
|
83
|
+
api.worker = channel.worker; // back-compat: expose the raw Worker
|
|
84
|
+
return api;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* One-shot convenience: spawn/connect a worker, dispatch a single seed, return the stabilized snapshot, tear down.
|
|
89
|
+
* @param {object} opts createGraphWorker opts + { seed, settleTimeout }
|
|
90
|
+
* @returns {Promise<object>} snapshot (graph.serialize())
|
|
91
|
+
*/
|
|
92
|
+
function spawnGraph( opts ) {
|
|
93
|
+
opts = opts || {};
|
|
94
|
+
var w = createGraphWorker(opts);
|
|
95
|
+
return w.dispatch(opts.seed, opts).then(
|
|
96
|
+
function ( snap ) { return Promise.resolve(w.terminate()).then(function () { return snap; }); },
|
|
97
|
+
function ( err ) { return Promise.resolve(w.terminate()).then(function () { throw err; }); }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* One-shot BOUNDED invoke (P1): spawn/connect a worker, invoke a single seed, return { summary, writeFootprint } —
|
|
103
|
+
* ONLY the declared frontier alphabet (Σ_sep) crosses back, not the whole snapshot — tear down.
|
|
104
|
+
* @param {object} opts createGraphWorker opts + { seed, boundedFrom, boundedKeys, settleTimeout }
|
|
105
|
+
* @returns {Promise<{summary:object, writeFootprint:string[]}>}
|
|
106
|
+
*/
|
|
107
|
+
function invokeGraph( opts ) {
|
|
108
|
+
opts = opts || {};
|
|
109
|
+
var w = createGraphWorker(opts);
|
|
110
|
+
return w.invoke(opts).then(
|
|
111
|
+
function ( res ) { return Promise.resolve(w.terminate()).then(function () { return res; }); },
|
|
112
|
+
function ( err ) { return Promise.resolve(w.terminate()).then(function () { throw err; }); }
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* serveGraphWorker(opts) → Promise<{ server, address, close }>. Stand up a socket runtime that accepts sub-graph
|
|
118
|
+
* dispatches from remote `createGraphWorker({ address, … })` clients. opts.path (unix) or opts.port[/host] (TCP),
|
|
119
|
+
* or opts.address directly. `close()` stops accepting and resolves when the server is down.
|
|
120
|
+
*/
|
|
121
|
+
function serveGraphWorker( opts ) {
|
|
122
|
+
opts = opts || {};
|
|
123
|
+
var serve = require('./transport-socket.js').serve;
|
|
124
|
+
var listen = opts.address || (opts.path != null ? { path: opts.path } : { port: opts.port, host: opts.host });
|
|
125
|
+
return serve(listen).then(function ( server ) {
|
|
126
|
+
return {
|
|
127
|
+
server: server,
|
|
128
|
+
address: server.address(),
|
|
129
|
+
close: function () { return new Promise(function ( res ) { server.close(function () { res(); }); }); }
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { createGraphWorker: createGraphWorker, spawnGraph: spawnGraph, invokeGraph: invokeGraph, serveGraphWorker: serveGraphWorker };
|
|
135
|
+
module.exports.createInvokePool = require('./invoke-pool.js').createInvokePool; // P3: warm-worker pool keyed by contract (N cases → 1 instance)
|