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,148 @@
|
|
|
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
|
+
* lattice-pack — the `.sgc` TYPED-LATTICE PACKAGE (host-side, ZERO-CORE, fs-free). The THIRD sibling of
|
|
10
|
+
* `corpus-pack.js` (kind `'corpus'` = the authored grammar tree) and `method-pack.js` (kind `'methods'` =
|
|
11
|
+
* the learned method library): THIS one packages the REGISTRY — the typed isa/enum vocabulary + the
|
|
12
|
+
* synonym RINGS grown at runtime by the admission gate (`registry.js#decideRingAdmission` /
|
|
13
|
+
* `mergeRingProposals`) + their `ringProvenance`. So a GROWN lattice (the runtime-learned vocabulary edges,
|
|
14
|
+
* e.g. from the SemEval-T9 / hypernymy campaign) MOVES between deployments — not just survives a restart.
|
|
15
|
+
*
|
|
16
|
+
* One `.sgc` envelope (`format:'sgc'` + `sgcVersion` + `manifest`), discriminated by `kind` — here
|
|
17
|
+
* `'lattice'`. A reader dispatches on `kind`; corpus-pack / method-pack are untouched.
|
|
18
|
+
*
|
|
19
|
+
* const bundle = packLattice(freezeRegistry(reg, 'v3'), { name: 'isa', version: 'v3' }); // .sgc JSON
|
|
20
|
+
* const r = loadLattice(bundle, hostRegistry, { version: 'v3' }); // grow the canon
|
|
21
|
+
* // r.registry = the host registry GROWN by the shipped rings (confluence-checked); r.admitted/rejected
|
|
22
|
+
*
|
|
23
|
+
* THE PORTABLE UNIT is the registry value object `{ version, frozen, keys:{ <key>:{ tier, enum, synonyms,
|
|
24
|
+
* grain, … } }, ringProvenance }` — already plain JSON and already version-stamped (`freezeRegistry` /
|
|
25
|
+
* `bumpVersion`), so packing is a clone + a derived, self-describing manifest.
|
|
26
|
+
*
|
|
27
|
+
* THE LOAD-BEARING SOUNDNESS LINE (the SAME B8 doctrine as method-pack, plus the ring's own admission gate):
|
|
28
|
+
* The registry IS the closed typed vocabulary (any tier) + the defeasible ring envelope; its identity is
|
|
29
|
+
* the version token. Loading a packaged lattice into a receiving host is sound IFF versions AGREE — else
|
|
30
|
+
* the same key may now carry a DIFFERENT enum/ring, so no verbatim adoption (a stale ring would mis-canon
|
|
31
|
+
* a downstream fact). So the gate covers loading:
|
|
32
|
+
* - versions AGREE → grow the host: each shipped ring alias is re-proposed through `mergeRingProposals`,
|
|
33
|
+
* so it enters ONLY if its member is a registered enum member of the host key AND the ring stays
|
|
34
|
+
* CONFLUENT (the critical-pair check) — a conflicting shipped alias is REJECTED, never blindly merged.
|
|
35
|
+
* With NO host registry, the packaged registry is ADOPTED wholesale (the portable canon).
|
|
36
|
+
* - versions DIFFER → grow NOTHING (the host re-derives / re-learns). The bundle stays readable via
|
|
37
|
+
* `unpackLattice` for a host that wants to inspect it ("refuse beats a stale canon").
|
|
38
|
+
* Versions are opt-in (like `cache.js#version` / method-pack): the gate enforces iff BOTH sides declare a
|
|
39
|
+
* version (absent ⇒ permissive — the host explicitly opted out of pinning).
|
|
40
|
+
*/
|
|
41
|
+
const { mergeRingProposals } = require('./registry.js');
|
|
42
|
+
|
|
43
|
+
function clone( x ) { return x == null ? x : JSON.parse(JSON.stringify(x)); }
|
|
44
|
+
|
|
45
|
+
// B8 gating (verbatim doctrine of method-pack#versionsAgree): active iff BOTH sides declare a version.
|
|
46
|
+
function versionsAgree( vHost, vPkg ) {
|
|
47
|
+
if ( vHost == null || vPkg == null ) return true; // no pinning requested → permissive
|
|
48
|
+
return vHost === vPkg;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** the flat ring PROPOSALS of a registry — one `{key, member, alias}` per (member, alias) in every key's ring. */
|
|
52
|
+
function ringsOf( reg ) {
|
|
53
|
+
const out = [], keys = (reg && reg.keys) || {};
|
|
54
|
+
for ( const key of Object.keys(keys) ) {
|
|
55
|
+
const syn = keys[key].synonyms;
|
|
56
|
+
if ( !syn ) continue;
|
|
57
|
+
for ( const member of Object.keys(syn) )
|
|
58
|
+
for ( const alias of (syn[member] || []) ) out.push({ key, member, alias });
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Derive the self-describing SCHEMA of a registry — what a receiver needs to know without reading bodies.
|
|
65
|
+
* @returns {{ keyCount, tier1Keys, enumKeys:[…], ringMembers, ringAliases }}
|
|
66
|
+
*/
|
|
67
|
+
function deriveLatticeSchema( reg ) {
|
|
68
|
+
const keys = (reg && reg.keys) || {};
|
|
69
|
+
const enumKeys = []; let tier1 = 0, ringMembers = 0, ringAliases = 0;
|
|
70
|
+
for ( const k of Object.keys(keys) ) {
|
|
71
|
+
const e = keys[k];
|
|
72
|
+
if ( e.tier === 1 ) tier1++;
|
|
73
|
+
if ( e.enum ) enumKeys.push(k);
|
|
74
|
+
if ( e.synonyms ) for ( const m of Object.keys(e.synonyms) ) { ringMembers++; ringAliases += (e.synonyms[m] || []).length; }
|
|
75
|
+
}
|
|
76
|
+
return { keyCount: Object.keys(keys).length, tier1Keys: tier1, enumKeys: enumKeys.sort(), ringMembers, ringAliases };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Pack a registry (typed isa lattice + grown rings) into a portable `.sgc` lattice bundle (plain JSON).
|
|
81
|
+
* @param registry a registry (from `deriveRegistry` / `freezeRegistry` / the merged runtime container).
|
|
82
|
+
* @param opts { name, version, description } — version defaults to the registry's own stamp.
|
|
83
|
+
*/
|
|
84
|
+
function packLattice( registry, opts ) {
|
|
85
|
+
opts = opts || {};
|
|
86
|
+
registry = registry || { keys: {} };
|
|
87
|
+
return {
|
|
88
|
+
format : 'sgc',
|
|
89
|
+
sgcVersion: 1,
|
|
90
|
+
kind : 'lattice',
|
|
91
|
+
manifest : {
|
|
92
|
+
name : opts.name || 'lattice',
|
|
93
|
+
version : opts.version || registry.version || '0.0.0',
|
|
94
|
+
description: opts.description || '',
|
|
95
|
+
frozen : !!registry.frozen,
|
|
96
|
+
schema : deriveLatticeSchema(registry)
|
|
97
|
+
},
|
|
98
|
+
registry : clone(registry)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Unpack a `.sgc` lattice bundle. Pure read — returns the registry + manifest + a version-gate verdict (does
|
|
104
|
+
* NOT mutate any host). Throws on a non-lattice bundle.
|
|
105
|
+
* @param bundle a packLattice() output
|
|
106
|
+
* @param opts { hostVersion } — compared against the package version for `loadSafe`.
|
|
107
|
+
* @returns {{ registry, manifest, schema, versionPackage, loadSafe }}
|
|
108
|
+
*/
|
|
109
|
+
function unpackLattice( bundle, opts ) {
|
|
110
|
+
opts = opts || {};
|
|
111
|
+
if ( !bundle || bundle.format !== 'sgc' ) throw new Error('not an .sgc bundle');
|
|
112
|
+
if ( bundle.kind !== 'lattice' ) throw new Error('not a .sgc lattice bundle (kind=' + bundle.kind + ')');
|
|
113
|
+
const registry = bundle.registry || { keys: {} };
|
|
114
|
+
const manifest = bundle.manifest || {};
|
|
115
|
+
return {
|
|
116
|
+
registry, manifest, schema: manifest.schema || deriveLatticeSchema(registry),
|
|
117
|
+
versionPackage: manifest.version, loadSafe: versionsAgree(opts.hostVersion, manifest.version)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Load a `.sgc` lattice bundle INTO a host registry, gated by the version (B8). On a version MATCH it GROWS
|
|
123
|
+
* the host: with a host registry, each shipped ring alias is re-proposed through `mergeRingProposals` (member
|
|
124
|
+
* ∈ enum ∧ confluence re-checked) so a conflicting alias is REJECTED, never blindly merged; with NO host
|
|
125
|
+
* registry, the packaged registry is ADOPTED wholesale. On a MISMATCH it grows nothing (the host re-learns).
|
|
126
|
+
*
|
|
127
|
+
* @param bundle a packLattice() output
|
|
128
|
+
* @param hostRegistry the host registry to grow (or null/undefined to ADOPT the packaged one)
|
|
129
|
+
* @param opts { version } — the host's canon version (B8). Omit to opt out of version pinning.
|
|
130
|
+
* @returns {{ registry, adopted, merged, admitted, rejected, skipped, loadSafe, versionHost, versionPackage }}
|
|
131
|
+
*/
|
|
132
|
+
function loadLattice( bundle, hostRegistry, opts ) {
|
|
133
|
+
opts = opts || {};
|
|
134
|
+
const hostVersion = opts.version != null ? opts.version : (hostRegistry && hostRegistry.version);
|
|
135
|
+
const { registry: pkg, versionPackage } = unpackLattice(bundle, { hostVersion });
|
|
136
|
+
const safe = versionsAgree(hostVersion, versionPackage);
|
|
137
|
+
const shipped = ringsOf(pkg);
|
|
138
|
+
if ( !safe ) // version mismatch → refuse to inject a stale canon; the host re-derives / re-learns.
|
|
139
|
+
return { registry: hostRegistry || null, adopted: false, merged: false, admitted: [], rejected: [], skipped: shipped.length, loadSafe: false, versionHost: hostVersion, versionPackage };
|
|
140
|
+
if ( !hostRegistry ) // no host canon → adopt the portable one wholesale (its own rings come along).
|
|
141
|
+
return { registry: clone(pkg), adopted: true, merged: false, admitted: shipped, rejected: [], skipped: 0, loadSafe: true, versionHost: hostVersion, versionPackage };
|
|
142
|
+
// grow the host by the shipped rings through the SAME admission gate (confluence-checked, provenance-tagged).
|
|
143
|
+
const proposals = shipped.map(( r ) => ({ key: r.key, member: r.member, alias: r.alias, via: 'loaded:' + (bundle.manifest && bundle.manifest.name || 'lattice') }));
|
|
144
|
+
const r = mergeRingProposals(hostRegistry, proposals);
|
|
145
|
+
return { registry: r.registry, adopted: false, merged: true, admitted: r.admitted, rejected: r.rejected, skipped: 0, loadSafe: true, versionHost: hostVersion, versionPackage };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { packLattice, unpackLattice, loadLattice, deriveLatticeSchema, ringsOf };
|
|
@@ -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
|
+
* leaf-io — the TYPED LEAF I/O DISCIPLINE (ZERO-CORE, host-side). Closes the tiered-plan gap (ii): the executor's
|
|
10
|
+
* raw reply ("area=120", "72 km/h", "yes.") polluted the projection pool, so downstream leaves consumed garbage and
|
|
11
|
+
* the reassembled answer was unreadable. This generalizes the demo's execProgram/cellule idiom to prose leaves:
|
|
12
|
+
* the leaf's OUTPUT is parsed to a BARE TYPED VALUE (number | yes/no) or REFUSED — never carried garbled. Fail-closed:
|
|
13
|
+
* an ambiguous reply (two numbers, prose) is a typed refusal with a blame, not a guess.
|
|
14
|
+
*
|
|
15
|
+
* const { parseLeafValue, LEAF_ANSWER_SYSTEM } = require('skynet-graph/lib/authoring/leaf-io');
|
|
16
|
+
* const out = await ask({ system: LEAF_ANSWER_SYSTEM, user: leaf.prompt, maxTokens: 16 });
|
|
17
|
+
* const v = parseLeafValue(out); // { ok:true, kind:'number', value:24 } | { ok:false, blame }
|
|
18
|
+
* if ( !v.ok ) refuseLeaf(v.blame); else post(v.value);
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// the executor-side contract matching the parser (kept WITH it so prompt and parse never drift apart).
|
|
22
|
+
const LEAF_ANSWER_SYSTEM = 'Solve this single small step. Reply with ONLY the bare result: one number (no units, '
|
|
23
|
+
+ 'no commas, no symbols, no variable name), or yes/no. Nothing else.';
|
|
24
|
+
// the any-kind contract (non-numeric domains, e.g. table lookups whose answer is a cell text): same discipline,
|
|
25
|
+
// the text option is a VERBATIM copy (a name/label), never a sentence.
|
|
26
|
+
const LEAF_ANSWER_SYSTEM_ANY = 'Solve this single small step. Reply with ONLY the bare result: one number (no units, '
|
|
27
|
+
+ 'no commas, no symbols, no variable name), yes/no, or the exact text value copied verbatim (a name or label, '
|
|
28
|
+
+ 'not a sentence). Nothing else.';
|
|
29
|
+
|
|
30
|
+
// a short bare string that may enter the pool as a TEXT value — never a sentence, never a refusal phrase
|
|
31
|
+
// (a "cannot/unknown" surface must stay a typed refusal: the pool feeds downstream leaves, garbage is worse there).
|
|
32
|
+
const REFUSAL_RE = /\b(cannot|can't|unable|unknown|not sure|no answer|don't know|sorry)\b|^i\s/i;
|
|
33
|
+
const isBareText = ( s ) => !!s && s.length <= 56 && !/\n/.test(s) && s.split(/\s+/).length <= 6 && !REFUSAL_RE.test(s);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* parseLeafValue(raw, opts) → { ok:true, kind:'number'|'bool'|'text', value } | { ok:false, blame, raw }
|
|
37
|
+
* Deterministic, fail-closed. Tolerated surface noise: a `name=`/`name:` echo (takes the RHS), one leading $, comma
|
|
38
|
+
* thousand-separators, a trailing unit word after the number, trailing punctuation, yes/no casing. REFUSED: empty,
|
|
39
|
+
* no numeric/boolean token, several DISTINCT numeric candidates (ambiguity is never resolved by guessing).
|
|
40
|
+
* @param opts.kind 'number' | 'bool' — restrict the accepted type ('auto' default accepts number|bool) ;
|
|
41
|
+
* 'text' — a short VERBATIM string only ; 'any' — number preferred, then bool, then short text (the
|
|
42
|
+
* non-numeric front-door: a digit-bearing cell text like "32, 44" is TEXT under `any`, ambiguous under auto).
|
|
43
|
+
*/
|
|
44
|
+
function parseLeafValue( raw, opts ) {
|
|
45
|
+
opts = opts || {};
|
|
46
|
+
const want = opts.kind || 'auto';
|
|
47
|
+
let s = String(raw == null ? '' : raw).trim().replace(/^["'`\s]+|["'`.\s]+$/g, '');
|
|
48
|
+
if ( !s ) return { ok: false, blame: 'empty reply', raw: raw };
|
|
49
|
+
if ( s.indexOf('=') >= 0 ) s = s.slice(s.lastIndexOf('=') + 1).trim(); // "area=120" / "6*4=24" → the declared result (RHS)
|
|
50
|
+
const lbl = s.match(/^[a-z_][\w .]*:\s*(.+)$/i); // "result: 120" → the RHS (alphabetic label only — "2:30" stays ambiguous)
|
|
51
|
+
if ( lbl ) s = lbl[1].trim();
|
|
52
|
+
if ( want !== 'number' && want !== 'text' ) {
|
|
53
|
+
const b = s.match(/^(yes|no|true|false)\b[.!]?$/i);
|
|
54
|
+
if ( b ) { const y = /^(yes|true)/i.test(b[1]); return { ok: true, kind: 'bool', value: y ? 'yes' : 'no' }; }
|
|
55
|
+
}
|
|
56
|
+
if ( want === 'bool' ) return { ok: false, blame: 'not a yes/no reply', raw: raw };
|
|
57
|
+
if ( want !== 'text' ) {
|
|
58
|
+
// numeric: the reply must carry exactly ONE numeric value (a unit word after it is tolerated, a second number is not).
|
|
59
|
+
const nums = s.match(/-?\$?\d[\d,]*(?:\.\d+)?/g) || [];
|
|
60
|
+
const distinct = Array.from(new Set(nums.map(( n ) => Number(n.replace(/[$,]/g, '')) )));
|
|
61
|
+
if ( distinct.length === 1 ) {
|
|
62
|
+
// the remainder must be a value+unit surface, not a sentence re-stating the problem (fail-closed on prose).
|
|
63
|
+
const stripped = s.replace(/-?\$?\d[\d,]*(?:\.\d+)?/g, ' ').replace(/[%°]/g, ' ').trim();
|
|
64
|
+
if ( !(/\s/.test(stripped) && stripped.split(/\s+/).length > 2) ) return { ok: true, kind: 'number', value: distinct[0] };
|
|
65
|
+
if ( want !== 'any' ) return { ok: false, blame: 'prose reply, not a bare value', raw: raw };
|
|
66
|
+
}
|
|
67
|
+
else if ( want !== 'any' ) {
|
|
68
|
+
if ( distinct.length === 0 ) return { ok: false, blame: 'no numeric value in reply', raw: raw };
|
|
69
|
+
return { ok: false, blame: 'ambiguous reply (' + distinct.length + ' distinct numbers)', raw: raw };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// text (kind 'text' | 'any' fall-through): a short verbatim value, never prose, never a refusal phrase.
|
|
73
|
+
if ( isBareText(s) ) return { ok: true, kind: 'text', value: s };
|
|
74
|
+
return { ok: false, blame: 'not a bare short text value', raw: raw };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { parseLeafValue, LEAF_ANSWER_SYSTEM, LEAF_ANSWER_SYSTEM_ANY };
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
* library — the method-library INDEX + dispatch: the JUNCTURE of the two grammars (host-side, ZERO-CORE; study
|
|
10
|
+
* doc/WIP/studies/2026-06-30-creative-loop-two-level-grammar.md).
|
|
11
|
+
*
|
|
12
|
+
* The STRUCTURING grammar describes an abstract mechanism as a target FrontierSignature (params `role:sort` + the
|
|
13
|
+
* signature keys); the CONCEPT-DLL grammar is the learned library of crystallized methods. Dispatch = an **O(1)
|
|
14
|
+
* dictionary lookup on the canonical `libraryKey`** (the bucket) → refine the bucket by each candidate's APPLICATION
|
|
15
|
+
* CONDITIONS (the NACs) over the call-site scope → a RANKED list (weighted = "plusieurs façons"). It is NEVER a
|
|
16
|
+
* search/parse over the corpus — that is the HRG-parsing NP cliff (Lange-Welzl 1987). This is structure-mapping
|
|
17
|
+
* (Gentner): the abstract mechanism is the relational structure, the library methods are the source domains, a match
|
|
18
|
+
* is the analogy. The interface alphabet (`libraryKey` + `appConditions`) IS the snapped separator between the two
|
|
19
|
+
* grammars; keeping it K1-canonical is exactly what makes dispatch a lookup instead of a search.
|
|
20
|
+
*
|
|
21
|
+
* const lib = makeLibrary();
|
|
22
|
+
* indexMethod(lib, crystalCandidate, { weight: 2 }); // index a crystallizeStructural candidate
|
|
23
|
+
* const r = dispatch(lib, { frontier, signatureKeys }, scopeFacts);
|
|
24
|
+
* // r.candidates = ranked methods whose app-conditions hold at this site; r.scanned = bucket size (<< r.total)
|
|
25
|
+
*/
|
|
26
|
+
const { libraryKey } = require('./crystallize.js');
|
|
27
|
+
const { holdsAtoms } = require('./contract.js');
|
|
28
|
+
const { digest } = require('../providers/canonicalize.js');
|
|
29
|
+
|
|
30
|
+
const frontierOf = ( c ) => c && (c.frontier || (c.schema && c.schema.frontier)) || { params: [] };
|
|
31
|
+
const keyOf = ( c ) => (c && (c.libraryKey || (c.schema && c.schema.libraryKey))) || libraryKey(frontierOf(c), (c && c.signatureKeys) || []);
|
|
32
|
+
|
|
33
|
+
/** A fresh, empty method library (a `libraryKey` → entries dictionary + a flat list). */
|
|
34
|
+
function makeLibrary() { return { byKey: Object.create(null), methods: [] }; }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Index a crystallized method under its `libraryKey`.
|
|
38
|
+
* @param candidate a `crystallizeStructural` candidate ({ schema:{frontier,libraryKey,…}, signatureKeys, … }).
|
|
39
|
+
* @param opts.weight production weight for ranking (default 1) — the home for `semiring.js`/inside-outside scores.
|
|
40
|
+
* @returns the libraryKey it was indexed under.
|
|
41
|
+
*/
|
|
42
|
+
function indexMethod( lib, candidate, opts ) {
|
|
43
|
+
opts = opts || {};
|
|
44
|
+
const key = keyOf(candidate);
|
|
45
|
+
const entry = { candidate, key, weight: opts.weight == null ? 1 : opts.weight };
|
|
46
|
+
(lib.byKey[key] = lib.byKey[key] || []).push(entry);
|
|
47
|
+
lib.methods.push(entry);
|
|
48
|
+
return key;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The O(1) bucket for a target signature — the LOOKUP (never a scan over the corpus). target = { frontier,
|
|
52
|
+
* signatureKeys } | { libraryKey }. */
|
|
53
|
+
function bucketOf( lib, target ) {
|
|
54
|
+
const key = (target && target.libraryKey) || libraryKey((target && target.frontier) || { params: [] }, (target && target.signatureKeys) || []);
|
|
55
|
+
return { key, entries: lib.byKey[key] || [] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Do a method's application conditions (the NACs reified on its FrontierSignature) hold at a call site? require keys
|
|
59
|
+
* must be PRESENT (engine semantics — present, not truthy); assert atoms must HOLD (`contract.holdsAtoms`, the single
|
|
60
|
+
* source of truth, handles `$ref` + bare keys via the safe `expr.js`). An empty appConditions ⇒ trivially applicable. */
|
|
61
|
+
function appConditionsHold( frontier, scopeFacts ) {
|
|
62
|
+
const ac = (frontier && frontier.appConditions) || {};
|
|
63
|
+
const facts = scopeFacts || {};
|
|
64
|
+
for ( const k of (ac.require || []) ) if ( !(k in facts) ) return false;
|
|
65
|
+
for ( const a of (ac.assert || []) ) if ( typeof a === 'string' && a.trim() && !holdsAtoms(facts, a) ) return false;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Dispatch a target FrontierSignature against the library: O(1) bucket lookup → refine by app-conditions over the
|
|
71
|
+
* scope → ranked candidates.
|
|
72
|
+
* @returns { key, candidates:[{candidate,weight,key}], scanned, total }
|
|
73
|
+
* candidates the bucket entries whose app-conditions hold, sorted by descending weight (ties keep index order).
|
|
74
|
+
* scanned the bucket size — proves dispatch touched ONLY the bucket, never the whole corpus (the lookup invariant).
|
|
75
|
+
* total the library size (for the scanned << total assertion).
|
|
76
|
+
*/
|
|
77
|
+
function dispatch( lib, target, scopeFacts, opts ) {
|
|
78
|
+
opts = opts || {};
|
|
79
|
+
const { key, entries } = bucketOf(lib, target);
|
|
80
|
+
const weightOf = opts.weight || (( e ) => e.weight);
|
|
81
|
+
const matched = entries
|
|
82
|
+
.filter(( e ) => appConditionsHold(frontierOf(e.candidate), scopeFacts))
|
|
83
|
+
.sort(( a, b ) => weightOf(b) - weightOf(a));
|
|
84
|
+
return { key, candidates: matched, scanned: entries.length, total: lib.methods.length };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The dropped NACs of a candidate at a site = the application-conditions that FAIL (so dispatch would drop it). The
|
|
89
|
+
* SAME predicates as `appConditionsHold` (`require` by PRESENCE — engine present-not-truthy, finding #32; `assert` by
|
|
90
|
+
* `holdsAtoms`), so the loosened path and the exact path never disagree. NEVER use `contract.satisfies` for the require
|
|
91
|
+
* half — it tests truthiness and disagrees on present-falsy keys (Laurie confront pt2). Empty ⇒ the appConditions hold.
|
|
92
|
+
*/
|
|
93
|
+
function droppedNACs( frontier, scopeFacts ) {
|
|
94
|
+
const ac = (frontier && frontier.appConditions) || {}, facts = scopeFacts || {};
|
|
95
|
+
const require = [], assert = [];
|
|
96
|
+
for ( const k of (ac.require || []) ) if ( !(k in facts) ) require.push(k);
|
|
97
|
+
for ( const a of (ac.assert || []) ) if ( typeof a === 'string' && a.trim() && !holdsAtoms(facts, a) ) assert.push(a);
|
|
98
|
+
return { require, assert };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* §6.2 INTERFACE-ONLY (loosened) dispatch — the FUZZY-RECALL arm of the library (spec §3.4, refined by the Laurie
|
|
103
|
+
* confront). Same O(1) `libraryKey` bucket as `dispatch` (the structural interface — NEVER widened: fuzzing it is the
|
|
104
|
+
* GPTCache false-merge / K1 defeat, pt4), but instead of DROPPING the NAC-failing in-bucket candidates it SURFACES them
|
|
105
|
+
* as `proposals` — ADAPT-SKELETON sources (NOT replay candidates). The controller (adapt.js#adaptOrForge, interfaceRecall)
|
|
106
|
+
* re-forges the differing content into a NEW method with SITE-derived appConditions, then the EXACT verify gates THAT
|
|
107
|
+
* method's own contract — so a proposal can only MISS (→ fresh forge) or be REJECTED, never write an unsound fact.
|
|
108
|
+
* @returns { key, exact, proposals:[{candidate,weight,key,droppedNACs,dropCount}], scanned, total }
|
|
109
|
+
* exact — the bucket entries whose appConditions HOLD (== `dispatch` candidates), weight-ranked.
|
|
110
|
+
* proposals — NAC-failing in-bucket donors, ranked by VERSION-SPACE SPECIFICITY (fewest NACs dropped first, the
|
|
111
|
+
* least-adaptation climb; weight tie-break), capped at opts.k (default 3).
|
|
112
|
+
*/
|
|
113
|
+
function dispatchInterface( lib, target, scopeFacts, opts ) {
|
|
114
|
+
opts = opts || {};
|
|
115
|
+
const { key, entries } = bucketOf(lib, target);
|
|
116
|
+
const weightOf = opts.weight || (( e ) => e.weight);
|
|
117
|
+
const exact = [], proposals = [];
|
|
118
|
+
for ( const e of entries ) {
|
|
119
|
+
const dropped = droppedNACs(frontierOf(e.candidate), scopeFacts);
|
|
120
|
+
if ( !dropped.require.length && !dropped.assert.length ) exact.push(e);
|
|
121
|
+
else proposals.push(Object.assign({}, e, { droppedNACs: dropped, dropCount: dropped.require.length + dropped.assert.length }));
|
|
122
|
+
}
|
|
123
|
+
exact.sort(( a, b ) => weightOf(b) - weightOf(a));
|
|
124
|
+
proposals.sort(( a, b ) => (a.dropCount - b.dropCount) || (weightOf(b) - weightOf(a)));
|
|
125
|
+
return { key, exact, proposals: proposals.slice(0, opts.k == null ? 3 : opts.k), scanned: entries.length, total: lib.methods.length };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* DRIFT-side invalidation at the CATALOG grain — the companion of master-loop's `drift()` (which only
|
|
130
|
+
* evicts the exact-result cache + recall index). Without it, a drifted problem's next FORGE would
|
|
131
|
+
* re-dispatch the SAME stale method and re-`hit` its template at 0 calls — resurrecting exactly what
|
|
132
|
+
* drift invalidated (the correct-on-drift claim breaks). This deletes the template keyed by THIS
|
|
133
|
+
* site's signature from every in-bucket candidate: the stale unit only — the method skeleton, its
|
|
134
|
+
* other signature classes, and its class knowledge (frontier/contract/appConditions) survive, so the
|
|
135
|
+
* next solve re-adapts/re-forges just the violated entry (and re-indexes a fresh template).
|
|
136
|
+
* @param target { frontier, signatureKeys } | { libraryKey } — as `dispatch`.
|
|
137
|
+
* @param scopeFacts the drifted call-site facts (the signature source).
|
|
138
|
+
* @returns { key, invalidated:[{ id, sig }] }
|
|
139
|
+
*/
|
|
140
|
+
function invalidateTemplate( lib, target, scopeFacts ) {
|
|
141
|
+
const { key, entries } = bucketOf(lib, target);
|
|
142
|
+
const invalidated = [];
|
|
143
|
+
for ( const e of entries ) {
|
|
144
|
+
const cand = e.candidate || {};
|
|
145
|
+
const proj = {};
|
|
146
|
+
for ( const k of (cand.signatureKeys || []) ) if ( scopeFacts && k in scopeFacts ) proj[k] = scopeFacts[k];
|
|
147
|
+
const sig = digest(proj);
|
|
148
|
+
if ( cand.templatesBySig && sig in cand.templatesBySig ) {
|
|
149
|
+
delete cand.templatesBySig[sig];
|
|
150
|
+
invalidated.push({ id: cand.schema && cand.schema._id, sig });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { key, invalidated };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { makeLibrary, indexMethod, bucketOf, appConditionsHold, dispatch, dispatchInterface, droppedNACs, frontierOf, invalidateTemplate };
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
* lifecycle — the UNIFIED plasticity ledger (host-side, ZERO-CORE; study
|
|
21
|
+
* doc/WIP/studies/2026-06-26-…, pass 4).
|
|
22
|
+
*
|
|
23
|
+
* Every concept (a mined mini-NN, an LLM-calling expert, a hand-written rule) carries ONE
|
|
24
|
+
* scalar — its **plasticity** p ∈ [0,1] — the unified creativity/learning knob:
|
|
25
|
+
* p = 1 fully plastic — learning ON / high creativity (NN: noise+lr; LLM: high temperature)
|
|
26
|
+
* p = 0 frozen — deterministic, memo-perfect, the consolidated spine (temperature 0)
|
|
27
|
+
* 0<p<1 partial — the "personality" regime (some creativity retained)
|
|
28
|
+
* The discrete regimes {plastic, probationary, frozen} are just the rounding/banding of p.
|
|
29
|
+
*
|
|
30
|
+
* Consolidation = Complementary-Learning-Systems annealing: a concept is born plastic and p
|
|
31
|
+
* decays toward 0 as its reliability is PROVEN (the success of the derivations it's in, snapped
|
|
32
|
+
* to a band via stats.bandOf). A concept that doesn't prove out stays plastic (keeps exploring);
|
|
33
|
+
* a proven one freezes (joins the stable, auditable, memo-hot spine).
|
|
34
|
+
*
|
|
35
|
+
* DISCIPLINE (K1): plasticity lives HERE, in the ledger — it MODULATES a provider (temperature,
|
|
36
|
+
* learning-rate, exploration noise) and the host's promotion decisions; it is NEVER a fact that
|
|
37
|
+
* gates applicability (a continuous gate would churn the memo). And `record(name, ok)` takes the
|
|
38
|
+
* GENUINE outcome — a retraction that was the intended iterative trial (#15) is not a failure;
|
|
39
|
+
* the host decides `ok`, the ledger only tallies.
|
|
40
|
+
*
|
|
41
|
+
* const lc = createLifecycle();
|
|
42
|
+
* lc.register('Crystal_A_B'); // born plastic
|
|
43
|
+
* lc.record('Crystal_A_B', verifiedOk); // each verified outcome anneals p
|
|
44
|
+
* const temp = lc.plasticity('Crystal_A_B'); // thread into the provider (LLM temp / NN noise)
|
|
45
|
+
*/
|
|
46
|
+
const { bandOf } = require('../providers/stats.js');
|
|
47
|
+
|
|
48
|
+
const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
|
|
49
|
+
|
|
50
|
+
function createLifecycle( opts ) {
|
|
51
|
+
opts = opts || {};
|
|
52
|
+
const minTrials = opts.minTrials == null ? 2 : opts.minTrials; // explore until there's evidence
|
|
53
|
+
const maxRank = opts.maxRank == null ? 3 : opts.maxRank; // bandOf's top rank (certain)
|
|
54
|
+
const bands = opts.bands;
|
|
55
|
+
// Default anneal: plastic (1) until enough trials, then p decays with the snapped reliability
|
|
56
|
+
// band — unproven/low stays plastic, 'certain' freezes (p=0). Override via opts.anneal(rep).
|
|
57
|
+
const anneal = opts.anneal || function ( rep ) {
|
|
58
|
+
if ( rep.trials < minTrials ) return 1;
|
|
59
|
+
return clamp01(1 - rep.rank / maxRank);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const led = {}; // name -> { successes, trials, p }
|
|
63
|
+
|
|
64
|
+
function rep( name ) {
|
|
65
|
+
const e = led[name] || { successes: 0, trials: 0 };
|
|
66
|
+
const theta = e.trials ? e.successes / e.trials : 0;
|
|
67
|
+
const b = bandOf(theta, bands);
|
|
68
|
+
return { successes: e.successes, trials: e.trials, theta: theta, rank: b.rank, label: b.label };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
/** Register a concept — born fully plastic (p=1) unless opts.p0 given. */
|
|
73
|
+
register: function ( name, o ) { led[name] = { successes: 0, trials: 0, p: (o && o.p0 != null) ? clamp01(o.p0) : 1 }; return this; },
|
|
74
|
+
/** Record a GENUINE outcome; re-anneals plasticity. */
|
|
75
|
+
record: function ( name, ok ) {
|
|
76
|
+
const e = led[name] || (led[name] = { successes: 0, trials: 0, p: 1 });
|
|
77
|
+
e.trials++; if ( ok ) e.successes++;
|
|
78
|
+
e.p = anneal(rep(name));
|
|
79
|
+
return this;
|
|
80
|
+
},
|
|
81
|
+
/** The unified plasticity knob p∈[0,1] (default 1 for an unknown concept). */
|
|
82
|
+
plasticity: function ( name ) { return led[name] ? led[name].p : 1; },
|
|
83
|
+
/** The rounded regime of p — a convenience view, not a separate state. */
|
|
84
|
+
regime: function ( name ) { const p = this.plasticity(name); return p >= 0.75 ? 'plastic' : p < 0.25 ? 'frozen' : 'probationary'; },
|
|
85
|
+
/** {successes, trials, theta, rank, label} — the evidence behind p. */
|
|
86
|
+
reputation: function ( name ) { return rep(name); },
|
|
87
|
+
/** Snapshot of the whole pool. */
|
|
88
|
+
all: function () { const o = {}; for ( const k in led ) o[k] = { p: led[k].p, regime: this.regime(k), reputation: rep(k) }; return o; },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { createLifecycle };
|