skynet-graph 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/CONTRIBUTING.md +22 -0
  2. package/LICENSE +661 -0
  3. package/README.md +245 -0
  4. package/bin/sg +20 -0
  5. package/concepts/README.md +23 -0
  6. package/concepts/_substrate/Claim/Confidence/Trusted.json +6 -0
  7. package/concepts/_substrate/Claim/Confidence.json +10 -0
  8. package/concepts/_substrate/Claim/Freshness/Stale.json +7 -0
  9. package/concepts/_substrate/Claim/Freshness.json +5 -0
  10. package/concepts/_substrate/Claim/Verification/Refuted.json +6 -0
  11. package/concepts/_substrate/Claim/Verification.json +6 -0
  12. package/concepts/_substrate/Claim.json +4 -0
  13. package/concepts/_substrate/Frontier/InBeam.json +6 -0
  14. package/concepts/_substrate/Frontier/Stuck.json +6 -0
  15. package/concepts/_substrate/Frontier.json +5 -0
  16. package/concepts/_substrate/Intake/ToTask.json +22 -0
  17. package/concepts/_substrate/Intake/Typed.json +10 -0
  18. package/concepts/_substrate/Intake.json +27 -0
  19. package/concepts/_substrate/Task/Answer.json +12 -0
  20. package/concepts/_substrate/Task/Complexity/Atomic.json +5 -0
  21. package/concepts/_substrate/Task/Complexity/Compound/Expansion.json +7 -0
  22. package/concepts/_substrate/Task/Complexity/Compound.json +6 -0
  23. package/concepts/_substrate/Task/Complexity.json +6 -0
  24. package/concepts/_substrate/Task/EvalComplexity.json +13 -0
  25. package/concepts/_substrate/Task/ReportUp.json +6 -0
  26. package/concepts/_substrate/Task/Rollup.json +10 -0
  27. package/concepts/_substrate/Task.json +5 -0
  28. package/concepts/clinical/Diagnosis/Medication.json +6 -0
  29. package/concepts/clinical/Diagnosis.json +15 -0
  30. package/concepts/clinical/Observation/LabValue/OutOfRange.json +6 -0
  31. package/concepts/clinical/Observation/LabValue.json +5 -0
  32. package/concepts/clinical/Observation.json +6 -0
  33. package/concepts/common/Document/pathBasket.json +6 -0
  34. package/concepts/common/Document.json +3 -0
  35. package/concepts/common/Edge/Distance.json +13 -0
  36. package/concepts/common/Edge/Stay/LongStay.json +8 -0
  37. package/concepts/common/Edge/Stay.json +8 -0
  38. package/concepts/common/Edge/Travel/LongTravel.json +16 -0
  39. package/concepts/common/Edge/Travel/ShortTravel.json +11 -0
  40. package/concepts/common/Edge/Travel/targetNode.json +7 -0
  41. package/concepts/common/Edge/Travel.json +7 -0
  42. package/concepts/common/Edge.json +3 -0
  43. package/concepts/common/Vertice.json +3 -0
  44. package/concepts/supply/Fulfillment.json +10 -0
  45. package/concepts/supply/Inventory/Reorder.json +7 -0
  46. package/concepts/supply/Inventory.json +5 -0
  47. package/concepts/supply/Procurement/SupplierConfirm.json +6 -0
  48. package/concepts/supply/Procurement.json +8 -0
  49. package/concepts/supply/Transport.json +5 -0
  50. package/doc/API.md +556 -0
  51. package/doc/CAPABILITIES.md +373 -0
  52. package/doc/MODELISATION.md +511 -0
  53. package/doc/architecture.md +267 -0
  54. package/doc/concept-as-graph.md +379 -0
  55. package/doc/concept-learning.md +168 -0
  56. package/doc/creative-loop-map.md +202 -0
  57. package/doc/original-2016-doc.md +148 -0
  58. package/doc/usage.md +501 -0
  59. package/lib/authoring/abstract.js +401 -0
  60. package/lib/authoring/abstraction.js +190 -0
  61. package/lib/authoring/adapt.js +393 -0
  62. package/lib/authoring/ancestry.js +216 -0
  63. package/lib/authoring/author.js +147 -0
  64. package/lib/authoring/bounded-merge.js +57 -0
  65. package/lib/authoring/canon.js +158 -0
  66. package/lib/authoring/clock.js +123 -0
  67. package/lib/authoring/combinator.js +92 -0
  68. package/lib/authoring/compete.js +76 -0
  69. package/lib/authoring/compose-hotspot.js +314 -0
  70. package/lib/authoring/compress.js +279 -0
  71. package/lib/authoring/concept-net.js +254 -0
  72. package/lib/authoring/concepts.js +66 -0
  73. package/lib/authoring/context-project.js +277 -0
  74. package/lib/authoring/contract.js +476 -0
  75. package/lib/authoring/corpus-pack.js +125 -0
  76. package/lib/authoring/cost-probe.js +86 -0
  77. package/lib/authoring/crystallize.js +555 -0
  78. package/lib/authoring/dag-decompose.js +256 -0
  79. package/lib/authoring/dataset-adapter.js +253 -0
  80. package/lib/authoring/debug-provider.js +92 -0
  81. package/lib/authoring/decompose.js +343 -0
  82. package/lib/authoring/emittability.js +236 -0
  83. package/lib/authoring/equilibrium.js +160 -0
  84. package/lib/authoring/extract.js +153 -0
  85. package/lib/authoring/forest.js +55 -0
  86. package/lib/authoring/forge-fallback.js +116 -0
  87. package/lib/authoring/givens.js +121 -0
  88. package/lib/authoring/glossary.js +177 -0
  89. package/lib/authoring/grammar-graph.js +191 -0
  90. package/lib/authoring/granularity.js +80 -0
  91. package/lib/authoring/graph-net.js +108 -0
  92. package/lib/authoring/ground.js +148 -0
  93. package/lib/authoring/higher-order.js +85 -0
  94. package/lib/authoring/hotspot.js +132 -0
  95. package/lib/authoring/hysteresis.js +85 -0
  96. package/lib/authoring/index.js +106 -0
  97. package/lib/authoring/lattice-morphism.js +61 -0
  98. package/lib/authoring/lattice-pack.js +148 -0
  99. package/lib/authoring/leaf-io.js +77 -0
  100. package/lib/authoring/library.js +156 -0
  101. package/lib/authoring/lifecycle.js +92 -0
  102. package/lib/authoring/loop.js +193 -0
  103. package/lib/authoring/master-loop.js +132 -0
  104. package/lib/authoring/mdl.js +209 -0
  105. package/lib/authoring/memo-stability.js +122 -0
  106. package/lib/authoring/method-explorer.js +138 -0
  107. package/lib/authoring/method-pack.js +181 -0
  108. package/lib/authoring/method.js +202 -0
  109. package/lib/authoring/mine.js +510 -0
  110. package/lib/authoring/mount.js +119 -0
  111. package/lib/authoring/negotiate.js +100 -0
  112. package/lib/authoring/parametric.js +139 -0
  113. package/lib/authoring/reaggregate.js +75 -0
  114. package/lib/authoring/rebalance.js +157 -0
  115. package/lib/authoring/recall.js +135 -0
  116. package/lib/authoring/registry.js +350 -0
  117. package/lib/authoring/relearn.js +147 -0
  118. package/lib/authoring/retention.js +89 -0
  119. package/lib/authoring/segment-proxy.js +114 -0
  120. package/lib/authoring/serve-leaf.js +64 -0
  121. package/lib/authoring/slot-aware-serve.js +77 -0
  122. package/lib/authoring/sound-invoke.js +59 -0
  123. package/lib/authoring/split-serve.js +74 -0
  124. package/lib/authoring/ste.js +239 -0
  125. package/lib/authoring/stock.js +101 -0
  126. package/lib/authoring/store.js +94 -0
  127. package/lib/authoring/supervise.js +138 -0
  128. package/lib/authoring/support.js +131 -0
  129. package/lib/authoring/task-mirror.js +51 -0
  130. package/lib/authoring/typed-loop.js +160 -0
  131. package/lib/authoring/validate.js +569 -0
  132. package/lib/authoring/widen.js +88 -0
  133. package/lib/combos/appliance.js +189 -0
  134. package/lib/combos/critique.js +377 -0
  135. package/lib/combos/defaults.js +86 -0
  136. package/lib/combos/durable-runner.js +115 -0
  137. package/lib/combos/forge.js +209 -0
  138. package/lib/combos/index.js +86 -0
  139. package/lib/combos/learning-library.js +253 -0
  140. package/lib/combos/mixture-serve.js +193 -0
  141. package/lib/combos/plan-loop.js +192 -0
  142. package/lib/combos/proxy-cache.js +272 -0
  143. package/lib/combos/self-mod.js +94 -0
  144. package/lib/durable/audit.js +102 -0
  145. package/lib/durable/checkpoint-store.js +550 -0
  146. package/lib/durable/fold.js +62 -0
  147. package/lib/durable/interpreter.js +242 -0
  148. package/lib/durable/xlate.js +185 -0
  149. package/lib/graph/Graph.js +2486 -0
  150. package/lib/graph/expr.js +307 -0
  151. package/lib/graph/index.js +26 -0
  152. package/lib/graph/log.js +134 -0
  153. package/lib/graph/objects/Concept.js +382 -0
  154. package/lib/graph/objects/Entity.js +503 -0
  155. package/lib/graph/objects/Node.js +57 -0
  156. package/lib/graph/objects/PathMap.js +557 -0
  157. package/lib/graph/objects/Segment.js +100 -0
  158. package/lib/graph/tasks/stabilize.js +59 -0
  159. package/lib/graph/tasks/taskflow.js +247 -0
  160. package/lib/index.js +122 -0
  161. package/lib/load.js +132 -0
  162. package/lib/providers/backends.js +117 -0
  163. package/lib/providers/borderline.js +129 -0
  164. package/lib/providers/cache.js +226 -0
  165. package/lib/providers/canonicalize.js +198 -0
  166. package/lib/providers/constat.js +107 -0
  167. package/lib/providers/geo.js +53 -0
  168. package/lib/providers/index.js +116 -0
  169. package/lib/providers/intake.js +263 -0
  170. package/lib/providers/llm-local.js +88 -0
  171. package/lib/providers/llm.js +236 -0
  172. package/lib/providers/local-host.js +197 -0
  173. package/lib/providers/merge-consistency.js +162 -0
  174. package/lib/providers/nogood.js +149 -0
  175. package/lib/providers/reason-loop.js +223 -0
  176. package/lib/providers/semiring.js +285 -0
  177. package/lib/providers/solver-fork.js +163 -0
  178. package/lib/providers/stats.js +228 -0
  179. package/lib/providers/verify.js +120 -0
  180. package/lib/runtime/index.js +135 -0
  181. package/lib/runtime/invoke-pool.js +92 -0
  182. package/lib/runtime/protocol.js +210 -0
  183. package/lib/runtime/transport-socket.js +77 -0
  184. package/lib/runtime/worker-entry.js +31 -0
  185. package/lib/sg/cli.js +601 -0
  186. package/lib/sg/log-sinks.js +214 -0
  187. package/lib/sg/mcp.js +395 -0
  188. package/lib/sg/proxy-run.js +66 -0
  189. package/lib/sg/serve-demo.js +185 -0
  190. package/lib/sg/serve.js +205 -0
  191. package/lib/sg/trace.js +118 -0
  192. package/lib/studio/protocol.js +45 -0
  193. package/lib/studio/public/app.js +310 -0
  194. package/lib/studio/public/components/ConceptEditor.js +43 -0
  195. package/lib/studio/public/components/ConceptTree.js +43 -0
  196. package/lib/studio/public/components/CorpusPanel.js +68 -0
  197. package/lib/studio/public/components/ForkTree.js +36 -0
  198. package/lib/studio/public/components/GrammarGraph.js +99 -0
  199. package/lib/studio/public/components/GraphCanvas.js +156 -0
  200. package/lib/studio/public/components/Inspector.js +48 -0
  201. package/lib/studio/public/components/LearningPanel.js +85 -0
  202. package/lib/studio/public/components/PromptConsole.js +46 -0
  203. package/lib/studio/public/components/ProviderTrace.js +39 -0
  204. package/lib/studio/public/components/SessionSplit.js +47 -0
  205. package/lib/studio/public/components/TilingOverlay.js +40 -0
  206. package/lib/studio/public/components/Timeline.js +57 -0
  207. package/lib/studio/public/index.html +26 -0
  208. package/lib/studio/public/styles.css +185 -0
  209. package/lib/studio/public/ws.js +58 -0
  210. package/lib/studio/server.js +130 -0
  211. package/lib/studio/session.js +397 -0
  212. package/lib/studio/studio.js +149 -0
  213. package/package.json +42 -0
@@ -0,0 +1,382 @@
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
+
19
+ var isArray = require('is').array;
20
+ var debug = require('../log').defaultLogger;// module fallback; applyTo rebinds to graph._log
21
+ var dmerge = require('deepmerge');
22
+ var { compileExpression } = require('../expr');
23
+
24
+ function Concept( _, graph, parent ) {
25
+ this.init(_, graph, parent);
26
+ };
27
+
28
+ Concept.prototype = {
29
+ _static : Concept,
30
+ /**
31
+ * Init & mount child concepts
32
+ * @param record
33
+ * @param graph
34
+ * @param parent
35
+ */
36
+ init : function ( record, graph, parent ) {
37
+ var me = this,
38
+ cKeys = record.childConcepts && Object.keys(record.childConcepts) || [];
39
+
40
+ me._schema = record;
41
+ graph._conceptLib[record._id] = this;
42
+ if ( cKeys.length ) {
43
+ this._openConcepts = {};
44
+ this._openConceptsRequires = {};
45
+
46
+ cKeys.map(
47
+ function ( v ) {
48
+ me._openConcepts[v] = new Concept(record.childConcepts[v], graph, me);
49
+ me._openConceptsRequires[v] =
50
+ isArray(record.childConcepts[v].require) ? record.childConcepts[v].require
51
+ : record.childConcepts[v].require && [record.childConcepts[v].require] || [];
52
+ }
53
+ );
54
+ }
55
+ else
56
+ this.isLeaf = true;
57
+
58
+ // generate the assert fn (asserts + ensure, &&-joined)
59
+ this._compileAssert();
60
+ this._id = record._id;
61
+ this._name = record._name;
62
+ this._parent = parent;
63
+
64
+
65
+ },
66
+ /**
67
+ * (Re)compile this concept's applicability test from its current `_schema`
68
+ * (assert + ensure expressions, &&-joined). Called at init and again by
69
+ * `patch()` so a hot-patched assert takes effect immediately.
70
+ */
71
+ _compileAssert: function () {
72
+ var record = this._schema,
73
+ asserts = isArray(record.assert) && record.assert
74
+ || record.assert && [record.assert]
75
+ || [],
76
+ ensure = isArray(record.ensure) && record.ensure
77
+ || record.ensure && [record.ensure]
78
+ || [];
79
+
80
+ asserts = asserts.concat(ensure);
81
+
82
+ var _assertFn = compileExpression(asserts, { empty: true });
83
+ this._assertTest = function ( scope, graph ) {
84
+ return _assertFn(function ( ref ) { return scope.getRef(ref); });
85
+ };
86
+ return this;
87
+ },
88
+ /**
89
+ * Hot-patch this expert: deep-merge `updates` into `_schema` and recompile
90
+ * the applicability test. Arrays (assert/ensure/require/provider) are
91
+ * REPLACED, not concatenated, so patching an assert overrides the old one.
92
+ * Re-evaluating live objects against the patched expert is the graph's job
93
+ * (see Graph.patchConcept).
94
+ * @param updates partial concept schema
95
+ */
96
+ patch : function ( updates ) {
97
+ this._schema = dmerge(this._schema, updates, { arrayMerge: function ( dest, src ) { return src; } });
98
+ this._compileAssert();
99
+ return this;
100
+ },
101
+ /**
102
+ * Apply-correlated context logger for a provider: ctx {concept,target,type,applyId}.
103
+ * A METHOD (not a stored property) because a Concept instance is shared across many
104
+ * scopes and concurrent async applies would race a mutable field. `applyId` is read
105
+ * from `scope._applyId` (set by applyTo for the duration of the apply). A provider
106
+ * that logs asynchronously should capture `const log = concept.log(scope)` EARLY so
107
+ * the child snapshots the right applyId. Used to later retrieve a concept's apply-logs
108
+ * via graph.logger.tail(n, {concept|applyId}) — without storing anything on the graph.
109
+ */
110
+ log : function ( scope ) {
111
+ var graph = scope._graph,
112
+ t = scope._ && (scope._.Node ? 'node' : scope._.Segment ? 'segment' : 'object');
113
+ return graph._log.child({
114
+ concept: this._name, conceptId: this._id,
115
+ target : scope._ && scope._._id, type: t, applyId: scope._applyId
116
+ });
117
+ },
118
+ /**
119
+ * Search Parent concept by name
120
+ * @param parent [Concept|undefined]
121
+ */
122
+ hasParent : function ( cn ) {
123
+ var c = this;
124
+ while ( c = c._parent )
125
+ if ( c._name === cn )
126
+ return c;
127
+ },
128
+ /**
129
+ * Search Parent concept by id
130
+ * @param parent [Concept|undefined]
131
+ */
132
+ hasParentId : function ( cid ) {
133
+ var c = this;
134
+ while ( c = c._parent )
135
+ if ( c._id === cid )
136
+ return c;
137
+ },
138
+ /**
139
+ * Return an async task that will cast the applicable concepts
140
+ * @param scope
141
+ * @param graph
142
+ * @returns {Function}
143
+ */
144
+ applyTo : function ( scope, graph ) {
145
+ var me = this;
146
+ // onCast detection: is this the not-cast -> cast transition? (symmetric to `cleaner`,
147
+ // which fires on cast -> uncast). Captured BEFORE we record the mapping: re-applies
148
+ // during a trial/iteration loop see it already mapped (no re-fire); an uncast deletes
149
+ // the mapping (Entity.unCast) so a later recast fires onCast again (re-subscribe).
150
+ var firstCast = !scope._mappedConcepts[me._name];
151
+ // will push or return task (or just inc some sema)
152
+ scope._mappedConcepts[me._name] = me;
153
+
154
+
155
+ return function ( graph, flow ) {
156
+ var debug = graph._log;// route the engine's own provider diagnostics per-graph
157
+
158
+ // oscillation backstop (#11.c.1): bound how many times this (target, concept)
159
+ // applies within an episode (the tally resets on each healthy settle). Over the
160
+ // ceiling -> record WHY in the `divergent` array fact + skip this apply; that
161
+ // fact is a non-cast condition (Concept.isApplicableTo) so the concept de-casts.
162
+ if ( graph._applyCount ) {
163
+ var _ck = scope._._id + '/' + me._name;
164
+ graph._applyCount[_ck] = (graph._applyCount[_ck] || 0) + 1;
165
+ if ( graph._applyCount[_ck] > graph._applyCap ) {
166
+ graph._markDivergent && graph._markDivergent(scope, me, graph._applyCount[_ck]);
167
+ return;
168
+ }
169
+ }
170
+
171
+ // Mint a per-apply id and expose it on the Entity (NOT on `scope._`, so it is
172
+ // never serialized) for the duration of this apply. It tags both the trace
173
+ // record (mkCtx below) and any logs the provider emits via concept.log(scope),
174
+ // so an apply's logs and its trace can be joined afterwards by applyId.
175
+ var applyId = ++graph._applyId;
176
+ scope._applyId = applyId;
177
+ var dlog = me.log(scope);// apply-correlated logger for the engine's own diagnostics
178
+
179
+ // trace context: attributes the mutation(s) this apply produces back to
180
+ // this concept (read by Graph.pushMutation -> cfg.onConceptApply).
181
+ var startTm = Date.now(),
182
+ why = me._computeWhy(scope),
183
+ mkCtx = function ( kind, ms ) {
184
+ return { conceptId: me._id, conceptName: me._name, targetId: scope._._id, kind: kind, why: why, ms: ms, applyId: applyId };
185
+ };
186
+
187
+ if ( me._schema.provider ) {// call the concept data provider
188
+ var p = isArray(me._schema.provider) ? me._schema.provider[0] : me._schema.provider,
189
+ argz = isArray(me._schema.provider) && me._schema.provider.slice(1),
190
+ providers = graph.static._providers, checkTm;
191
+
192
+ // stats
193
+
194
+ let execTm = Date.now();
195
+
196
+ p = p.split("::");
197
+ if ( providers[p[0]] && providers[p[0]][p[1]] ) {
198
+
199
+ flow.wait();// inc async flow
200
+ // P2 fix: flow.wait() is unconditional, so the lock MUST be released exactly once on
201
+ // every provider outcome — a mutation, NO mutation (idempotent skip), an error-only cb,
202
+ // or a synchronous throw. Releasing only inside the `r && pushMutation` callback leaked
203
+ // the lock whenever the provider returned no mutation, wedging the stabilize loop forever
204
+ // (`_stabilizing` stuck true). releaseFlow() guarantees a single balanced release.
205
+ var _released = false;
206
+ var releaseFlow = function () { if ( !_released ) { _released = true; flow.release(); } };
207
+
208
+ //debug.info(graph.cfg.label + " : Do provider ", p, 'on', scope._._id);
209
+ try {
210
+ checkTm = setTimeout(() => {
211
+ dlog.warn("still waiting on provider %s (25s)", p.join('::'))
212
+ }, 25000)
213
+ providers[p[0]][p[1]](
214
+ graph, me, scope, argz,
215
+ function ( e, r ) {
216
+ clearTimeout(checkTm);
217
+
218
+ // stats
219
+ graph._statsByProvider[p] = graph._statsByProvider[p] || 0;
220
+ graph._statsByProvider[p] += (Date.now() - execTm);
221
+
222
+ //debug.info(graph.cfg.label + " : Done provider ", p, 'on', scope._._id);
223
+ if ( r ) graph.pushMutation(r, scope._._id, 0, 0, 0, refs => {
224
+ //debug.info(graph.cfg.label + " : Done provider ", p, 'on', scope._._id);
225
+ releaseFlow()// w8 bagrefs b4 next cycle
226
+ }, mkCtx('provider', Date.now() - execTm));// so bagrefs will be w8 before the graph restart ... :/
227
+ else releaseFlow();// no mutation (idempotent skip / error-only cb) — still release or the loop never settles (P2)
228
+ e && dlog.warn("provider %s failed", p.join('::'), e);
229
+ });
230
+ } catch ( e ) {
231
+ releaseFlow();// provider threw synchronously after flow.wait() — release or deadlock (P2)
232
+ dlog.error("provider %s threw on %s", p.join('::'), scope._._id, e);
233
+ setTimeout(() => {
234
+ throw e
235
+ });
236
+ }
237
+
238
+ }
239
+ else {
240
+ scope.set(me._name, true, graph);// no provider wired -> flag the concept true so the graph can still settle
241
+ dlog.log("provider not found: %s", p.join('::'));
242
+ }
243
+
244
+ }
245
+ else if ( me._schema.type == "enum" ) {// enum are not used for now
246
+ graph.pushMutation(
247
+ {
248
+ $_id : "_parent",
249
+ [me._name]: me.isLeaf && [] || Object.keys(me._openConcepts)
250
+ },
251
+ scope._._id, 0, 0, 0, 0, mkCtx('enum', Date.now() - startTm)
252
+ );
253
+ }
254
+ else {
255
+ // scope.set(me._name, true, graph);
256
+ graph.pushMutation(
257
+ {
258
+ $_id : "_parent",
259
+ [me._name]: me._schema.defaultValue || true
260
+ },
261
+ scope._._id, 0, 0, 0, 0, mkCtx('default', Date.now() - startTm)
262
+ );
263
+ }
264
+
265
+ // if there a tpl in the concept definition apply it
266
+ if ( me._schema.applyMutations ) {
267
+ graph.pushMutation(me._schema.applyMutations, scope._._id, 0, 0, 0, 0, mkCtx('applyMutations', Date.now() - startTm));
268
+ }
269
+
270
+ // onCast lifecycle hook (the standing-cluster keystone) — symmetric to `cleaner`.
271
+ // Fires ONCE on the not-cast -> cast transition (firstCast), so a live subscription /
272
+ // handle is set up exactly once. Convention: store the handle on `scope._liveHandles`
273
+ // (off `_`, never serialized); the concept's `cleaner` tears it down on uncast. A
274
+ // re-apply does NOT re-fire it; an uncast -> recast does (re-subscribe). Balanced flow
275
+ // release on every outcome (mutation / none / throw) — the P2 lesson.
276
+ if ( firstCast && me._schema.onCast ) {
277
+ var op = isArray(me._schema.onCast) ? me._schema.onCast[0] : me._schema.onCast,
278
+ oargz = isArray(me._schema.onCast) && me._schema.onCast.slice(1),
279
+ oprov = graph.static._providers, ops = op.split("::");
280
+ if ( oprov[ops[0]] && oprov[ops[0]][ops[1]] ) {
281
+ flow.wait();
282
+ var _oReleased = false, oRelease = function () { if ( !_oReleased ) { _oReleased = true; flow.release(); } };
283
+ try {
284
+ oprov[ops[0]][ops[1]](graph, me, scope, oargz, function ( e, r ) {
285
+ if ( r ) graph.pushMutation(r, scope._._id, 0, 0, 0, oRelease, mkCtx('onCast', 0));
286
+ else oRelease();
287
+ e && dlog.warn("onCast %s failed", op, e);
288
+ });
289
+ } catch ( e ) { oRelease(); dlog.error("onCast %s threw on %s", op, scope._._id, e); }
290
+ }
291
+ }
292
+
293
+ // if the concept implies a graph sync (allowing concepts to be applied on server )
294
+ if ( me._schema.syncAfter ) {
295
+ // graph.stabilize(()=>graph.sync());
296
+ }
297
+ };
298
+ },
299
+ /**
300
+ * "Why it fired": for each `require`, the resolved value and the revision of
301
+ * the object that holds it (object-granular — the _rev of the last mutation
302
+ * that touched that object, not necessarily the specific key). Used by the
303
+ * trace (cfg.onConceptApply). Resolves without `follow` so it adds no watchers.
304
+ * @param scope the Entity the concept is being cast on
305
+ */
306
+ _computeWhy : function ( scope ) {
307
+ var requires = isArray(this._schema.require) && this._schema.require
308
+ || this._schema.require && [this._schema.require]
309
+ || [];
310
+ return requires.map(function ( c ) {
311
+ var value = scope.getRef(c), producedAtRev = null;
312
+ try {
313
+ if ( c.indexOf(':') !== -1 ) {
314
+ var box = scope.getRef(c.slice(0, c.lastIndexOf(':') + 1));// trailing-colon -> the object
315
+ producedAtRev = box && box._ ? box._._rev : (box && box._rev) || null;
316
+ }
317
+ else producedAtRev = scope._ && scope._._rev;
318
+ } catch ( e ) { producedAtRev = null; }
319
+ return { require: c, value: value, producedAtRev: producedAtRev };
320
+ });
321
+ },
322
+ /**
323
+ * Side-effect-free applicability gate — true iff this concept should cast on
324
+ * `scope`. A GATE, not a trigger: it only runs when the object is (re)tested.
325
+ * Checked in order:
326
+ * 1. autoCast:false → never auto-casts (manual/triggered only).
327
+ * 2. divergent → if the object carries a divergent record for this concept
328
+ * (blew the apply ceiling), refuse to (re)cast (#11.c.1).
329
+ * 3. require → every `require` ref must resolve; getRef(c, true) installs
330
+ * a FOLLOW watcher so the object is re-tested when a missing
331
+ * require later appears (this is how casting waits on inputs).
332
+ * 4. assert/ensure → the compiled _assertTest must be true.
333
+ * @param {Entity} scope
334
+ * @param {Graph} graph
335
+ * @returns {boolean}
336
+ */
337
+ isApplicableTo: function ( scope, graph ) {
338
+ if ( this._schema.autoCast === false ) {
339
+ return;
340
+ }
341
+
342
+ // divergent fact = non-cast condition (#11.c.1): a (target, concept) that blew the
343
+ // apply ceiling carries a reason record in `divergent`; it must not (re)cast.
344
+ var _dv = scope._ && scope._.divergent;
345
+ if ( _dv && _dv.length )
346
+ for ( var _i = 0; _i < _dv.length; _i++ )
347
+ if ( _dv[_i] && _dv[_i].concept === this._name ) return false;
348
+
349
+ var me = this,
350
+ requires = isArray(me._schema.require) && me._schema.require
351
+ || me._schema.require && [me._schema.require]
352
+ || [];
353
+ requires = requires.filter(function ( c ) {
354
+ // getRef(c, true): read the require AND install a follow-watcher, so the
355
+ // object is re-tested if this require is produced later. Keep the UNRESOLVED
356
+ // ones (0 is a valid value, not "missing").
357
+ var ref = scope.getRef(c, true);
358
+ return (!ref && (ref != 0)) && true || null;
359
+ });
360
+
361
+ // applicable iff no require is still unresolved AND the assert/ensure test holds
362
+ return !requires.length
363
+ && this._assertTest(scope, graph);
364
+ },
365
+ /**
366
+ * Remove the follow-watchers this concept's requires installed on `scope`
367
+ * (the inverse of the getRef(c, true) calls in isApplicableTo) — used when the
368
+ * concept is retracted so a defunct cast no longer destabilizes the object.
369
+ * @param scope
370
+ * @param graph
371
+ */
372
+ unRefRequires : function ( scope, graph ) {
373
+ var me = this,
374
+ requires = isArray(me._schema.require) && me._schema.require
375
+ || me._schema.require && [me._schema.require]
376
+ || [];
377
+ requires = requires.filter(function ( c ) {
378
+ return !graph.getRef(c, scope, true, true) && true || null;
379
+ });
380
+ }
381
+ };
382
+ module.exports = Concept;