@exellix/graph-engine 6.0.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 (202) hide show
  1. package/.env.example +3 -0
  2. package/CHANGELOG.md +208 -0
  3. package/README.md +827 -0
  4. package/dist/src/errors/ExellixGraphError.d.ts +38 -0
  5. package/dist/src/errors/ExellixGraphError.js +21 -0
  6. package/dist/src/errors/exellixGraphErrorCodes.d.ts +31 -0
  7. package/dist/src/errors/exellixGraphErrorCodes.js +32 -0
  8. package/dist/src/index.d.ts +100 -0
  9. package/dist/src/index.js +75 -0
  10. package/dist/src/inspection/contractInspection.d.ts +21 -0
  11. package/dist/src/inspection/contractInspection.js +526 -0
  12. package/dist/src/inspection/contractTypes.d.ts +137 -0
  13. package/dist/src/inspection/contractTypes.js +1 -0
  14. package/dist/src/inspection/controlInspection.d.ts +22 -0
  15. package/dist/src/inspection/controlInspection.js +130 -0
  16. package/dist/src/inspection/graphInspection.d.ts +51 -0
  17. package/dist/src/inspection/graphInspection.js +467 -0
  18. package/dist/src/inspection/index.d.ts +21 -0
  19. package/dist/src/inspection/index.js +17 -0
  20. package/dist/src/inspection/nodeInspection.d.ts +42 -0
  21. package/dist/src/inspection/nodeInspection.js +474 -0
  22. package/dist/src/inspection/types.d.ts +321 -0
  23. package/dist/src/inspection/types.js +14 -0
  24. package/dist/src/inspection/validateAiTasksNodeExtensions.d.ts +12 -0
  25. package/dist/src/inspection/validateAiTasksNodeExtensions.js +119 -0
  26. package/dist/src/inspection/validateCatalogPlanning.d.ts +21 -0
  27. package/dist/src/inspection/validateCatalogPlanning.js +187 -0
  28. package/dist/src/integrations/ActivityTrackerIntegration.d.ts +86 -0
  29. package/dist/src/integrations/ActivityTrackerIntegration.js +134 -0
  30. package/dist/src/integrations/ActivixGraphRunIntegration.d.ts +34 -0
  31. package/dist/src/integrations/ActivixGraphRunIntegration.js +338 -0
  32. package/dist/src/integrations/ActivixNodeActivityIntegration.d.ts +33 -0
  33. package/dist/src/integrations/ActivixNodeActivityIntegration.js +220 -0
  34. package/dist/src/integrations/cataloxGraphCatalog.d.ts +21 -0
  35. package/dist/src/integrations/cataloxGraphCatalog.js +30 -0
  36. package/dist/src/integrations/createActivixExellixIntegration.d.ts +14 -0
  37. package/dist/src/integrations/createActivixExellixIntegration.js +16 -0
  38. package/dist/src/integrations/createActivixFromEnv.d.ts +31 -0
  39. package/dist/src/integrations/createActivixFromEnv.js +53 -0
  40. package/dist/src/loaders/FileGraphLoader.d.ts +23 -0
  41. package/dist/src/loaders/FileGraphLoader.js +31 -0
  42. package/dist/src/playground/PlaygroundReporter.d.ts +40 -0
  43. package/dist/src/playground/PlaygroundReporter.js +480 -0
  44. package/dist/src/playground/index.d.ts +1 -0
  45. package/dist/src/playground/index.js +1 -0
  46. package/dist/src/runtime/ExellixGraphRuntime.d.ts +263 -0
  47. package/dist/src/runtime/ExellixGraphRuntime.js +1716 -0
  48. package/dist/src/runtime/GraphEngine.d.ts +33 -0
  49. package/dist/src/runtime/GraphEngine.js +4 -0
  50. package/dist/src/runtime/aiTasksObservability.d.ts +6 -0
  51. package/dist/src/runtime/aiTasksObservability.js +37 -0
  52. package/dist/src/runtime/aiTasksStrategyPhases.d.ts +46 -0
  53. package/dist/src/runtime/aiTasksStrategyPhases.js +93 -0
  54. package/dist/src/runtime/applyAiTaskProfileWebScopingToNarrix.d.ts +17 -0
  55. package/dist/src/runtime/applyAiTaskProfileWebScopingToNarrix.js +46 -0
  56. package/dist/src/runtime/buildAiTasksRunTaskRequest.d.ts +67 -0
  57. package/dist/src/runtime/buildAiTasksRunTaskRequest.js +164 -0
  58. package/dist/src/runtime/buildRunLog.d.ts +27 -0
  59. package/dist/src/runtime/buildRunLog.js +234 -0
  60. package/dist/src/runtime/buildRunTaskTaskConfigurationForward.d.ts +9 -0
  61. package/dist/src/runtime/buildRunTaskTaskConfigurationForward.js +80 -0
  62. package/dist/src/runtime/buildTaskNodeJobContext.d.ts +11 -0
  63. package/dist/src/runtime/buildTaskNodeJobContext.js +30 -0
  64. package/dist/src/runtime/canonicalModelUsed.d.ts +6 -0
  65. package/dist/src/runtime/canonicalModelUsed.js +36 -0
  66. package/dist/src/runtime/contextualScope.d.ts +7 -0
  67. package/dist/src/runtime/contextualScope.js +121 -0
  68. package/dist/src/runtime/dataFiltersEvaluation.d.ts +60 -0
  69. package/dist/src/runtime/dataFiltersEvaluation.js +169 -0
  70. package/dist/src/runtime/deepMerge.d.ts +5 -0
  71. package/dist/src/runtime/deepMerge.js +22 -0
  72. package/dist/src/runtime/events.d.ts +92 -0
  73. package/dist/src/runtime/events.js +122 -0
  74. package/dist/src/runtime/executionMatrixHost.d.ts +98 -0
  75. package/dist/src/runtime/executionMatrixHost.js +134 -0
  76. package/dist/src/runtime/executionVariableBuckets.d.ts +67 -0
  77. package/dist/src/runtime/executionVariableBuckets.js +96 -0
  78. package/dist/src/runtime/finalizers/errors.d.ts +9 -0
  79. package/dist/src/runtime/finalizers/errors.js +10 -0
  80. package/dist/src/runtime/finalizers/executeFinalizer.d.ts +40 -0
  81. package/dist/src/runtime/finalizers/executeFinalizer.js +471 -0
  82. package/dist/src/runtime/finalizers/schema.d.ts +18 -0
  83. package/dist/src/runtime/finalizers/schema.js +63 -0
  84. package/dist/src/runtime/finalizers/validateFinalizer.d.ts +16 -0
  85. package/dist/src/runtime/finalizers/validateFinalizer.js +534 -0
  86. package/dist/src/runtime/graphDocumentFingerprint.d.ts +8 -0
  87. package/dist/src/runtime/graphDocumentFingerprint.js +21 -0
  88. package/dist/src/runtime/graphEngineMemoryPaths.d.ts +12 -0
  89. package/dist/src/runtime/graphEngineMemoryPaths.js +55 -0
  90. package/dist/src/runtime/graphResponseMapping.d.ts +23 -0
  91. package/dist/src/runtime/graphResponseMapping.js +156 -0
  92. package/dist/src/runtime/graphResponseMigration.d.ts +7 -0
  93. package/dist/src/runtime/graphResponseMigration.js +44 -0
  94. package/dist/src/runtime/graphRunExecutionSeed.d.ts +29 -0
  95. package/dist/src/runtime/graphRunExecutionSeed.js +61 -0
  96. package/dist/src/runtime/graphRunIdentity.d.ts +7 -0
  97. package/dist/src/runtime/graphRunIdentity.js +18 -0
  98. package/dist/src/runtime/localSkills/deterministicRule.d.ts +137 -0
  99. package/dist/src/runtime/localSkills/deterministicRule.js +196 -0
  100. package/dist/src/runtime/localSkills/index.d.ts +12 -0
  101. package/dist/src/runtime/localSkills/index.js +14 -0
  102. package/dist/src/runtime/localSkills/memorixItemToScopedOutput.d.ts +7 -0
  103. package/dist/src/runtime/localSkills/memorixItemToScopedOutput.js +104 -0
  104. package/dist/src/runtime/localSkills/memorixRuntime.d.ts +9 -0
  105. package/dist/src/runtime/localSkills/memorixRuntime.js +70 -0
  106. package/dist/src/runtime/localSkills/memorixScopedConfig.d.ts +16 -0
  107. package/dist/src/runtime/localSkills/memorixScopedConfig.js +18 -0
  108. package/dist/src/runtime/localSkills/scopedAnswerAssembler.d.ts +23 -0
  109. package/dist/src/runtime/localSkills/scopedAnswerAssembler.js +35 -0
  110. package/dist/src/runtime/localSkills/scopedAnswerFields.d.ts +12 -0
  111. package/dist/src/runtime/localSkills/scopedAnswerFields.js +66 -0
  112. package/dist/src/runtime/localSkills/scopedAnswerWriter.d.ts +32 -0
  113. package/dist/src/runtime/localSkills/scopedAnswerWriter.js +156 -0
  114. package/dist/src/runtime/localSkills/scopedDataReader.d.ts +47 -0
  115. package/dist/src/runtime/localSkills/scopedDataReader.js +89 -0
  116. package/dist/src/runtime/localSkills/utils.d.ts +12 -0
  117. package/dist/src/runtime/localSkills/utils.js +39 -0
  118. package/dist/src/runtime/materializeStructuredRunTaskInput.d.ts +9 -0
  119. package/dist/src/runtime/materializeStructuredRunTaskInput.js +34 -0
  120. package/dist/src/runtime/memory.d.ts +51 -0
  121. package/dist/src/runtime/memory.js +250 -0
  122. package/dist/src/runtime/mergeExellixGraphRuntimeInvocation.d.ts +18 -0
  123. package/dist/src/runtime/mergeExellixGraphRuntimeInvocation.js +32 -0
  124. package/dist/src/runtime/modelConfigSelection.d.ts +7 -0
  125. package/dist/src/runtime/modelConfigSelection.js +37 -0
  126. package/dist/src/runtime/narrixIngestEnv.d.ts +9 -0
  127. package/dist/src/runtime/narrixIngestEnv.js +18 -0
  128. package/dist/src/runtime/pathExpr.d.ts +36 -0
  129. package/dist/src/runtime/pathExpr.js +131 -0
  130. package/dist/src/runtime/predicates.d.ts +14 -0
  131. package/dist/src/runtime/predicates.js +86 -0
  132. package/dist/src/runtime/readTaskNodeInputsConfig.d.ts +23 -0
  133. package/dist/src/runtime/readTaskNodeInputsConfig.js +27 -0
  134. package/dist/src/runtime/resolveExecutionPipelineForTaskNode.d.ts +11 -0
  135. package/dist/src/runtime/resolveExecutionPipelineForTaskNode.js +93 -0
  136. package/dist/src/runtime/resolveGraphEngineMemoryPaths.d.ts +63 -0
  137. package/dist/src/runtime/resolveGraphEngineMemoryPaths.js +213 -0
  138. package/dist/src/runtime/resolveModelConfigForNode.d.ts +20 -0
  139. package/dist/src/runtime/resolveModelConfigForNode.js +69 -0
  140. package/dist/src/runtime/resolveNarrixForTaskNode.d.ts +14 -0
  141. package/dist/src/runtime/resolveNarrixForTaskNode.js +19 -0
  142. package/dist/src/runtime/resolveTaskKey.d.ts +11 -0
  143. package/dist/src/runtime/resolveTaskKey.js +28 -0
  144. package/dist/src/runtime/resolveTaskNodeInputs.d.ts +25 -0
  145. package/dist/src/runtime/resolveTaskNodeInputs.js +140 -0
  146. package/dist/src/runtime/runTaskAugments.d.ts +17 -0
  147. package/dist/src/runtime/runTaskAugments.js +37 -0
  148. package/dist/src/runtime/runTaskResponse.d.ts +4 -0
  149. package/dist/src/runtime/runTaskResponse.js +13 -0
  150. package/dist/src/runtime/runtimeObjects.d.ts +85 -0
  151. package/dist/src/runtime/runtimeObjects.js +50 -0
  152. package/dist/src/runtime/smartInputPaths.d.ts +13 -0
  153. package/dist/src/runtime/smartInputPaths.js +38 -0
  154. package/dist/src/runtime/stepRetry.d.ts +21 -0
  155. package/dist/src/runtime/stepRetry.js +238 -0
  156. package/dist/src/runtime/synthesizedContextPipeline.d.ts +12 -0
  157. package/dist/src/runtime/synthesizedContextPipeline.js +28 -0
  158. package/dist/src/runtime/taskNodeConditionsEvaluation.d.ts +27 -0
  159. package/dist/src/runtime/taskNodeConditionsEvaluation.js +140 -0
  160. package/dist/src/runtime/taskNodeMainReadiness.d.ts +45 -0
  161. package/dist/src/runtime/taskNodeMainReadiness.js +164 -0
  162. package/dist/src/runtime/taskNodeRunTaskPreflight.d.ts +89 -0
  163. package/dist/src/runtime/taskNodeRunTaskPreflight.js +204 -0
  164. package/dist/src/runtime/validateCanonicalGraphDocument.d.ts +25 -0
  165. package/dist/src/runtime/validateCanonicalGraphDocument.js +567 -0
  166. package/dist/src/runtime/variables.d.ts +2 -0
  167. package/dist/src/runtime/variables.js +1 -0
  168. package/dist/src/runtime/withTimeout.d.ts +5 -0
  169. package/dist/src/runtime/withTimeout.js +20 -0
  170. package/dist/src/types/aiTaskProfile.d.ts +41 -0
  171. package/dist/src/types/aiTaskProfile.js +6 -0
  172. package/dist/src/types/aiTasksDerivedTypes.d.ts +5 -0
  173. package/dist/src/types/aiTasksDerivedTypes.js +1 -0
  174. package/dist/src/types/events.d.ts +23 -0
  175. package/dist/src/types/events.js +1 -0
  176. package/dist/src/types/job.d.ts +9 -0
  177. package/dist/src/types/job.js +1 -0
  178. package/dist/src/types/narrix.d.ts +60 -0
  179. package/dist/src/types/narrix.js +1 -0
  180. package/dist/src/types/options.d.ts +122 -0
  181. package/dist/src/types/options.js +1 -0
  182. package/dist/src/types/refs.d.ts +747 -0
  183. package/dist/src/types/refs.js +12 -0
  184. package/dist/src/types/results.d.ts +103 -0
  185. package/dist/src/types/results.js +1 -0
  186. package/dist/src/types/runLog.d.ts +72 -0
  187. package/dist/src/types/runLog.js +18 -0
  188. package/dist/src/types/taskNodeConfiguration.d.ts +95 -0
  189. package/dist/src/types/taskNodeConfiguration.js +3 -0
  190. package/dist/src/util/packageVersion.d.ts +2 -0
  191. package/dist/src/util/packageVersion.js +12 -0
  192. package/dist/testkit/RealTasksClient.d.ts +16 -0
  193. package/dist/testkit/RealTasksClient.js +143 -0
  194. package/dist/testkit/depGraphEngineFactory.d.ts +6 -0
  195. package/dist/testkit/depGraphEngineFactory.js +54 -0
  196. package/dist/testkit/exellixRuntimeObjects.d.ts +7 -0
  197. package/dist/testkit/exellixRuntimeObjects.js +25 -0
  198. package/dist/testkit/inMemoryGraphLoader.d.ts +6 -0
  199. package/dist/testkit/inMemoryGraphLoader.js +12 -0
  200. package/dist/testkit/index.d.ts +4 -0
  201. package/dist/testkit/index.js +4 -0
  202. package/package.json +70 -0
@@ -0,0 +1,42 @@
1
+ import type { Graph, GraphNode } from '../types/refs.js';
2
+ import type { NodeScopingQuestion, NodeScopingData, NodeMemoryShape, NodeNarrixDiscovery, NodeInspection } from './types.js';
3
+ /**
4
+ * Resolves the effective skillKey for a node. 5.x reads only the canonical `node.skillKey`.
5
+ * Returns undefined for finalizer nodes or task nodes that omit `skillKey`, so inspection stays non-throwing.
6
+ */
7
+ export declare function resolveNodeSkillKey(node: GraphNode): string | undefined;
8
+ /**
9
+ * Returns the scoping question, sources, and targets for a single node.
10
+ *
11
+ * All fields are derived statically from the node definition — no I/O.
12
+ */
13
+ export declare function getNodeScopingQuestion(node: GraphNode): NodeScopingQuestion;
14
+ /**
15
+ * Returns the conceptual execution-memory shape before and after a node runs.
16
+ *
17
+ * `upstreamWritePaths` should be the execution/output mapping paths of all upstream nodes.
18
+ * Pass `graph` when inspecting finalizers so question-driven item paths resolve the
19
+ * same way runtime resolves them.
20
+ */
21
+ export declare function getNodeMemoryShape(node: GraphNode, upstreamWritePaths?: string[], graph?: Graph): NodeMemoryShape;
22
+ /**
23
+ * Returns the Narrix discovery configuration for a node, or null if narrix
24
+ * is not configured on this node.
25
+ */
26
+ export declare function getNodeNarrixDiscovery(node: GraphNode): NodeNarrixDiscovery | null;
27
+ /**
28
+ * Fetches the actual x-scoped-data for the node's scoping question, given an entityId.
29
+ *
30
+ * Only meaningful for nodes with `skillKey: 'scoped-data-reader'`.
31
+ * For other nodes, `data` will be null and `error` will explain why.
32
+ */
33
+ export declare function fetchNodeScopingData(node: GraphNode, entityId: string, options?: {
34
+ opDb?: string;
35
+ }): Promise<NodeScopingData>;
36
+ /**
37
+ * Full per-node inspection: scoping question, memory shape, and narrix discovery.
38
+ *
39
+ * For graph-level context (before.expects), use `inspectGraph` which computes
40
+ * upstream paths automatically.
41
+ */
42
+ export declare function inspectNode(node: GraphNode, upstreamWritePaths?: string[], graph?: Graph): NodeInspection;
@@ -0,0 +1,474 @@
1
+ import { mapAiTaskProfileQuestionsToWebScopeQuestions } from '../runtime/applyAiTaskProfileWebScopingToNarrix.js';
2
+ import { readTaskNodeInputsConfig } from '../runtime/readTaskNodeInputsConfig.js';
3
+ import { runScopedDataReader } from '../runtime/localSkills/scopedDataReader.js';
4
+ // ---------------------------------------------------------------------------
5
+ // Internal helpers
6
+ // ---------------------------------------------------------------------------
7
+ function isTaskNode(node) {
8
+ return node.type !== 'finalizer';
9
+ }
10
+ function isFinalizerNode(node) {
11
+ return node.type === 'finalizer';
12
+ }
13
+ function isPlainObject(v) {
14
+ return v != null && typeof v === 'object' && !Array.isArray(v);
15
+ }
16
+ function findGraphNodeById(graph, nodeId) {
17
+ return graph.nodes.find((n) => String(n.id) === String(nodeId));
18
+ }
19
+ function pushDedupedRead(reads, path, source, memory) {
20
+ if (typeof path !== 'string' || path.length === 0)
21
+ return;
22
+ const existing = reads.find((r) => r.path === path && r.memory === memory);
23
+ if (!existing) {
24
+ reads.push({ path, memory, source });
25
+ return;
26
+ }
27
+ if (!existing.source) {
28
+ existing.source = source;
29
+ return;
30
+ }
31
+ const sources = existing.source.split(', ').filter(Boolean);
32
+ if (!sources.includes(source)) {
33
+ existing.source = [...sources, source].join(', ');
34
+ }
35
+ }
36
+ function finalizerInputReads(node) {
37
+ const reads = [];
38
+ for (const [label, binding] of Object.entries((node.inputs ?? {}))) {
39
+ if (binding.type === 'executionMemoryPath' || binding.type === 'outputsMemoryPath') {
40
+ pushDedupedRead(reads, binding.path, `inputs.${label}`, binding.type === 'outputsMemoryPath' ? 'outputsMemory' : 'executionMemory');
41
+ }
42
+ }
43
+ return reads;
44
+ }
45
+ function finalizerRuntimeReadSources(node, graph) {
46
+ const reads = finalizerInputReads(node);
47
+ const cfg = node.config;
48
+ if (node.finalizerType !== 'aggregate' || !isPlainObject(cfg)) {
49
+ return reads;
50
+ }
51
+ if (cfg.strategy === 'report-schema' && isPlainObject(cfg.sections)) {
52
+ for (const [key, spec] of Object.entries(cfg.sections)) {
53
+ if (!isPlainObject(spec))
54
+ continue;
55
+ pushDedupedRead(reads, spec.path, `config.sections.${key}`, 'executionMemory');
56
+ }
57
+ return reads;
58
+ }
59
+ if (cfg.strategy === 'question-driven') {
60
+ if (isPlainObject(cfg.meta)) {
61
+ for (const [key, binding] of Object.entries(cfg.meta)) {
62
+ if (!isPlainObject(binding))
63
+ continue;
64
+ pushDedupedRead(reads, binding.path, `config.meta.${key}`, binding.type === 'outputsMemoryPath' ? 'outputsMemory' : 'executionMemory');
65
+ }
66
+ }
67
+ if (isPlainObject(cfg.items)) {
68
+ for (const [key, spec] of Object.entries(cfg.items)) {
69
+ if (!isPlainObject(spec))
70
+ continue;
71
+ if (typeof spec.answerPath === 'string' && spec.answerPath.length > 0) {
72
+ pushDedupedRead(reads, spec.answerPath, `config.items.${key}.answerPath`, 'executionMemory');
73
+ continue;
74
+ }
75
+ if (!graph)
76
+ continue;
77
+ const nodeId = typeof spec.nodeId === 'string' ? spec.nodeId : undefined;
78
+ const referencedNode = nodeId ? findGraphNodeById(graph, nodeId) : undefined;
79
+ const fallbackPath = referencedNode?.executionMapping?.path;
80
+ pushDedupedRead(reads, fallbackPath, `config.items.${key}.node.executionMapping.path`, 'executionMemory');
81
+ }
82
+ }
83
+ }
84
+ return reads;
85
+ }
86
+ /**
87
+ * Resolves the effective skillKey for a node. 5.x reads only the canonical `node.skillKey`.
88
+ * Returns undefined for finalizer nodes or task nodes that omit `skillKey`, so inspection stays non-throwing.
89
+ */
90
+ export function resolveNodeSkillKey(node) {
91
+ if (isFinalizerNode(node))
92
+ return undefined;
93
+ const n = node;
94
+ return typeof n.skillKey === 'string' ? n.skillKey : undefined;
95
+ }
96
+ /**
97
+ * Classifies a jobContextMapping / inputs value string reference into a source type.
98
+ * Handles prefixes: executionMemory.* | jobMemory.* | variables.* | input.* | node.* | output.*
99
+ */
100
+ function classifyRefType(ref) {
101
+ if (ref.startsWith('jobMemory.'))
102
+ return 'jobMemory';
103
+ if (ref.startsWith('variables.'))
104
+ return 'variables';
105
+ // executionMemory.* is explicit; input.* and execution.* are shorthand execution memory paths
106
+ if (ref.startsWith('executionMemory.') ||
107
+ ref.startsWith('input.') ||
108
+ ref.startsWith('execution.') ||
109
+ ref.startsWith('node.') ||
110
+ ref.startsWith('output.')) {
111
+ return 'executionMemory';
112
+ }
113
+ return 'executionMemory';
114
+ }
115
+ /**
116
+ * Strip common prefixes to get the canonical dot-path used inside execution memory.
117
+ */
118
+ function stripRefPrefix(ref) {
119
+ const prefixes = ['executionMemory.', 'jobMemory.', 'variables.'];
120
+ for (const p of prefixes) {
121
+ if (ref.startsWith(p))
122
+ return ref.slice(p.length);
123
+ }
124
+ return ref;
125
+ }
126
+ /**
127
+ * Extract sources from a node's jobContextMapping.
128
+ */
129
+ function sourcesFromJobContextMapping(node) {
130
+ const mapping = node.jobContextMapping;
131
+ if (!mapping?.map)
132
+ return [];
133
+ return Object.entries(mapping.map).map(([label, ref]) => ({
134
+ type: classifyRefType(ref),
135
+ path: stripRefPrefix(ref),
136
+ label,
137
+ }));
138
+ }
139
+ /**
140
+ * Extract sources from runtime payload bindings (`inputsConfig`).
141
+ */
142
+ function sourcesFromInputBindings(node) {
143
+ const recipe = readTaskNodeInputsConfig(node);
144
+ const sources = [];
145
+ for (const [key, val] of Object.entries(recipe)) {
146
+ if (val == null || typeof val === 'string')
147
+ continue;
148
+ if (typeof val === 'object') {
149
+ const v = val;
150
+ if (v.type === 'executionMemoryPath' && typeof v.path === 'string') {
151
+ sources.push({ type: 'executionMemory', path: v.path, label: key });
152
+ continue;
153
+ }
154
+ if (typeof v['$path'] === 'string') {
155
+ const raw = v['$path'];
156
+ if (raw.startsWith('variables.'))
157
+ continue;
158
+ sources.push({ type: classifyRefType(raw), path: stripRefPrefix(raw), label: key });
159
+ }
160
+ }
161
+ }
162
+ return sources;
163
+ }
164
+ function variableRefsFromRecipe(recipe, labelPrefix) {
165
+ const sources = [];
166
+ for (const [key, val] of Object.entries(recipe)) {
167
+ if (val == null || typeof val !== 'object')
168
+ continue;
169
+ const raw = val['$path'];
170
+ if (typeof raw === 'string' && raw.startsWith('variables.')) {
171
+ sources.push({ type: 'variables', path: stripRefPrefix(raw), label: `${labelPrefix}.${key}` });
172
+ }
173
+ }
174
+ return sources;
175
+ }
176
+ /**
177
+ * Extract variable refs from `taskVariable` (and legacy `variables.*` refs still under `inputs`).
178
+ */
179
+ function sourcesFromTaskVariable(node) {
180
+ const sources = [];
181
+ if (node.taskVariable && typeof node.taskVariable === 'object') {
182
+ sources.push(...variableRefsFromRecipe(node.taskVariable, 'taskVariable'));
183
+ }
184
+ sources.push(...variableRefsFromRecipe(readTaskNodeInputsConfig(node), 'inputsConfig'));
185
+ return sources;
186
+ }
187
+ /**
188
+ * Extract scoped-data source if the node uses the `scoped-data-reader` skill.
189
+ */
190
+ function scopedDataSourceFromMetadata(node) {
191
+ const tc = node.taskConfiguration;
192
+ if (!tc)
193
+ return null;
194
+ const skillKey = resolveNodeSkillKey(node);
195
+ if (skillKey !== 'scoped-data-reader')
196
+ return null;
197
+ const { scopingMapId, entityIdPath, questionId, pack } = tc;
198
+ if (!scopingMapId && !questionId && !Array.isArray(pack))
199
+ return null;
200
+ const readability = node.metadata?.graphReadability;
201
+ return {
202
+ type: 'scopedData',
203
+ scopingMapId: typeof scopingMapId === 'string' ? scopingMapId : undefined,
204
+ entityIdPath: typeof entityIdPath === 'string' ? entityIdPath : undefined,
205
+ questionId: typeof questionId === 'string' ? questionId : undefined,
206
+ expectedKeys: Array.isArray(readability?.reads) ? readability.reads : undefined,
207
+ };
208
+ }
209
+ // ---------------------------------------------------------------------------
210
+ // Public API
211
+ // ---------------------------------------------------------------------------
212
+ /**
213
+ * Returns the scoping question, sources, and targets for a single node.
214
+ *
215
+ * All fields are derived statically from the node definition — no I/O.
216
+ */
217
+ export function getNodeScopingQuestion(node) {
218
+ const nodeId = node.id;
219
+ if (isFinalizerNode(node)) {
220
+ // Finalizer: no question, inputs are memory bindings.
221
+ const sources = Object.entries((node.inputs ?? {})).map(([label, binding]) => {
222
+ if (binding.type === 'executionMemoryPath' || binding.type === 'outputsMemoryPath') {
223
+ return {
224
+ type: binding.type === 'outputsMemoryPath' ? 'outputsMemory' : 'executionMemory',
225
+ path: binding.path,
226
+ label,
227
+ };
228
+ }
229
+ return { type: 'literal', label };
230
+ });
231
+ return {
232
+ nodeId,
233
+ name: undefined,
234
+ question: undefined,
235
+ kind: undefined,
236
+ sources,
237
+ targets: [],
238
+ };
239
+ }
240
+ const task = node;
241
+ const readability = task.metadata?.graphReadability;
242
+ const name = readability?.title;
243
+ const legacyInputsConfig = readTaskNodeInputsConfig(task);
244
+ const question = readability?.asks ??
245
+ (typeof task.taskVariable?.question === 'string' ? task.taskVariable.question : undefined) ??
246
+ (typeof legacyInputsConfig.question === 'string' ? legacyInputsConfig.question : undefined);
247
+ const kind = readability?.kind;
248
+ // Sources
249
+ const sources = [];
250
+ // 1. Scoped-data if applicable
251
+ const scopedSrc = scopedDataSourceFromMetadata(task);
252
+ if (scopedSrc) {
253
+ // Merge graphReadability.reads into expectedKeys if not already set
254
+ if (!scopedSrc.expectedKeys && Array.isArray(readability?.reads)) {
255
+ scopedSrc.expectedKeys = readability.reads;
256
+ }
257
+ sources.push(scopedSrc);
258
+ }
259
+ // 2. jobContextMapping map values
260
+ sources.push(...sourcesFromJobContextMapping(task));
261
+ // 3. input bindings + taskVariable variable refs
262
+ sources.push(...sourcesFromInputBindings(task));
263
+ sources.push(...sourcesFromTaskVariable(task));
264
+ // 4. smartInput.paths (graph-engine / ai-tasks 7.1+; legacy `paths: string[]` vs Rendrix-native SmartInputConfig)
265
+ const si = task.smartInput;
266
+ if (si && Array.isArray(si.paths)) {
267
+ for (const p of si.paths) {
268
+ if (typeof p === 'string' && p.length > 0) {
269
+ sources.push({ type: 'executionMemory', path: p, label: `smartInput:${p}` });
270
+ }
271
+ }
272
+ }
273
+ // 5. taskConfiguration.aiTaskProfile.inputSynthesis.sources when enabled
274
+ const isynth = task.taskConfiguration?.aiTaskProfile?.inputSynthesis;
275
+ if (isynth?.enabled && Array.isArray(isynth.sources)) {
276
+ for (const p of isynth.sources) {
277
+ if (typeof p === 'string' && p.length > 0) {
278
+ sources.push({ type: 'executionMemory', path: p, label: `inputSynthesis:${p}` });
279
+ }
280
+ }
281
+ }
282
+ // Targets
283
+ const targets = [];
284
+ if (task.executionMapping?.path) {
285
+ targets.push({
286
+ path: task.executionMapping.path,
287
+ memory: 'executionMemory',
288
+ mode: task.executionMapping.mode,
289
+ produces: Array.isArray(readability?.produces) ? readability.produces : undefined,
290
+ });
291
+ }
292
+ return { nodeId, name, question, kind, sources, targets };
293
+ }
294
+ /**
295
+ * Returns the conceptual execution-memory shape before and after a node runs.
296
+ *
297
+ * `upstreamWritePaths` should be the execution/output mapping paths of all upstream nodes.
298
+ * Pass `graph` when inspecting finalizers so question-driven item paths resolve the
299
+ * same way runtime resolves them.
300
+ */
301
+ export function getNodeMemoryShape(node, upstreamWritePaths, graph) {
302
+ const nodeId = node.id;
303
+ if (isFinalizerNode(node)) {
304
+ const reads = finalizerRuntimeReadSources(node, graph);
305
+ const outputsMemoryWrites = [];
306
+ if (node.outputMapping?.path) {
307
+ outputsMemoryWrites.push({ path: node.outputMapping.path, memory: 'outputsMemory', source: 'outputMapping' });
308
+ }
309
+ return {
310
+ nodeId,
311
+ before: {
312
+ reads,
313
+ expects: (upstreamWritePaths ?? []).map((p) => ({ path: p })),
314
+ },
315
+ after: { writes: [], outputsMemoryWrites },
316
+ };
317
+ }
318
+ const task = node;
319
+ // before.reads — from jobContextMapping.map source values
320
+ const reads = [];
321
+ const mapping = task.jobContextMapping?.map;
322
+ if (mapping) {
323
+ for (const [label, ref] of Object.entries(mapping)) {
324
+ reads.push({ path: stripRefPrefix(ref), source: `jobContextMapping.map.${label}` });
325
+ }
326
+ }
327
+ const inputsConfig = readTaskNodeInputsConfig(task);
328
+ for (const [key, val] of Object.entries(inputsConfig)) {
329
+ if (!val || typeof val !== 'object')
330
+ continue;
331
+ const v = val;
332
+ if ((v.type === 'executionMemoryPath' || v.type === 'outputsMemoryPath') && typeof v.path === 'string') {
333
+ reads.push({
334
+ path: v.path,
335
+ memory: v.type === 'outputsMemoryPath' ? 'outputsMemory' : 'executionMemory',
336
+ source: `inputsConfig.${key}`,
337
+ });
338
+ }
339
+ else if (typeof v['$path'] === 'string') {
340
+ reads.push({ path: stripRefPrefix(v['$path']), source: `inputsConfig.${key}` });
341
+ }
342
+ }
343
+ // after.writes — from executionMapping
344
+ const writes = [];
345
+ if (task.executionMapping?.path) {
346
+ writes.push({ path: task.executionMapping.path, memory: 'executionMemory', source: 'executionMapping' });
347
+ }
348
+ return {
349
+ nodeId,
350
+ before: {
351
+ reads,
352
+ expects: (upstreamWritePaths ?? []).map((p) => ({ path: p })),
353
+ },
354
+ after: { writes, outputsMemoryWrites: [] },
355
+ };
356
+ }
357
+ /**
358
+ * Returns the Narrix discovery configuration for a node, or null if narrix
359
+ * is not configured on this node.
360
+ */
361
+ export function getNodeNarrixDiscovery(node) {
362
+ if (isFinalizerNode(node))
363
+ return null;
364
+ const task = node;
365
+ const narrix = task.taskConfiguration?.narrix;
366
+ if (!narrix || typeof narrix !== 'object')
367
+ return null;
368
+ const n = narrix;
369
+ const web = task.taskConfiguration?.aiTaskProfile?.webScoping;
370
+ const enableWebScope = web?.enabled === true;
371
+ return {
372
+ nodeId: node.id,
373
+ enabled: true,
374
+ questionId: typeof n.questionId === 'string' ? n.questionId : undefined,
375
+ layer: typeof n.layer === 'string' ? n.layer : undefined,
376
+ narrativeTypeIds: Array.isArray(n.narrativeTypeIds) ? n.narrativeTypeIds : undefined,
377
+ webScope: {
378
+ enabled: enableWebScope,
379
+ forced: undefined,
380
+ questions: Array.isArray(web?.questions) && web.questions.length > 0
381
+ ? mapAiTaskProfileQuestionsToWebScopeQuestions(web.questions)
382
+ : undefined,
383
+ entityIdPath: undefined,
384
+ entityTypePath: undefined,
385
+ enrichFromRaw: undefined,
386
+ },
387
+ outputPaths: {
388
+ signals: 'executionMemory._narrix.scoping.signals',
389
+ stories: 'executionMemory._narrix.scoping.stories',
390
+ },
391
+ };
392
+ }
393
+ /**
394
+ * Fetches the actual x-scoped-data for the node's scoping question, given an entityId.
395
+ *
396
+ * Only meaningful for nodes with `skillKey: 'scoped-data-reader'`.
397
+ * For other nodes, `data` will be null and `error` will explain why.
398
+ */
399
+ export async function fetchNodeScopingData(node, entityId, options) {
400
+ const base = getNodeScopingQuestion(node);
401
+ if (isFinalizerNode(node)) {
402
+ return { ...base, entityId, data: null, error: 'finalizer nodes do not read scoped data' };
403
+ }
404
+ const task = node;
405
+ const skillKey = resolveNodeSkillKey(task);
406
+ if (skillKey !== 'scoped-data-reader') {
407
+ return {
408
+ ...base,
409
+ entityId,
410
+ data: null,
411
+ error: `node skillKey is "${skillKey ?? 'unset'}" — only scoped-data-reader nodes fetch x-scoped-data`,
412
+ };
413
+ }
414
+ const tc = (task.taskConfiguration ?? {});
415
+ const entityIdPath = typeof tc.entityIdPath === 'string' ? tc.entityIdPath : '_entityId';
416
+ // Build a synthetic executionMemory so runScopedDataReader resolves the entityId
417
+ const syntheticMemory = {};
418
+ const parts = entityIdPath.split('.');
419
+ let cursor = syntheticMemory;
420
+ for (let i = 0; i < parts.length - 1; i++) {
421
+ cursor[parts[i]] = {};
422
+ cursor = cursor[parts[i]];
423
+ }
424
+ cursor[parts[parts.length - 1]] = entityId;
425
+ const cfg = {
426
+ scopingMapId: typeof tc.scopingMapId === 'string' ? tc.scopingMapId : undefined,
427
+ questionId: typeof tc.questionId === 'string' ? tc.questionId : undefined,
428
+ entityIdPath,
429
+ opDb: options?.opDb ?? (typeof tc.opDb === 'string' ? tc.opDb : undefined),
430
+ pack: Array.isArray(tc.pack) ? tc.pack : undefined,
431
+ };
432
+ try {
433
+ const data = await runScopedDataReader(cfg, syntheticMemory);
434
+ return { ...base, entityId, data };
435
+ }
436
+ catch (err) {
437
+ return {
438
+ ...base,
439
+ entityId,
440
+ data: null,
441
+ error: err?.message ?? String(err),
442
+ };
443
+ }
444
+ }
445
+ /**
446
+ * Full per-node inspection: scoping question, memory shape, and narrix discovery.
447
+ *
448
+ * For graph-level context (before.expects), use `inspectGraph` which computes
449
+ * upstream paths automatically.
450
+ */
451
+ export function inspectNode(node, upstreamWritePaths, graph) {
452
+ const skillKey = resolveNodeSkillKey(node);
453
+ const readability = isTaskNode(node)
454
+ ? node.metadata?.graphReadability
455
+ : undefined;
456
+ let catalogBinding;
457
+ let catalogRequest;
458
+ if (isTaskNode(node)) {
459
+ const m = node.metadata;
460
+ catalogBinding = m?.catalogBinding;
461
+ catalogRequest = m?.catalogRequest;
462
+ }
463
+ return {
464
+ nodeId: node.id,
465
+ nodeType: node.type ?? 'task',
466
+ skillKey,
467
+ kind: readability?.kind,
468
+ scopingQuestion: getNodeScopingQuestion(node),
469
+ memoryShape: getNodeMemoryShape(node, upstreamWritePaths, graph),
470
+ narrixDiscovery: getNodeNarrixDiscovery(node),
471
+ catalogBinding,
472
+ catalogRequest,
473
+ };
474
+ }