@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.
- package/.env.example +3 -0
- package/CHANGELOG.md +208 -0
- package/README.md +827 -0
- package/dist/src/errors/ExellixGraphError.d.ts +38 -0
- package/dist/src/errors/ExellixGraphError.js +21 -0
- package/dist/src/errors/exellixGraphErrorCodes.d.ts +31 -0
- package/dist/src/errors/exellixGraphErrorCodes.js +32 -0
- package/dist/src/index.d.ts +100 -0
- package/dist/src/index.js +75 -0
- package/dist/src/inspection/contractInspection.d.ts +21 -0
- package/dist/src/inspection/contractInspection.js +526 -0
- package/dist/src/inspection/contractTypes.d.ts +137 -0
- package/dist/src/inspection/contractTypes.js +1 -0
- package/dist/src/inspection/controlInspection.d.ts +22 -0
- package/dist/src/inspection/controlInspection.js +130 -0
- package/dist/src/inspection/graphInspection.d.ts +51 -0
- package/dist/src/inspection/graphInspection.js +467 -0
- package/dist/src/inspection/index.d.ts +21 -0
- package/dist/src/inspection/index.js +17 -0
- package/dist/src/inspection/nodeInspection.d.ts +42 -0
- package/dist/src/inspection/nodeInspection.js +474 -0
- package/dist/src/inspection/types.d.ts +321 -0
- package/dist/src/inspection/types.js +14 -0
- package/dist/src/inspection/validateAiTasksNodeExtensions.d.ts +12 -0
- package/dist/src/inspection/validateAiTasksNodeExtensions.js +119 -0
- package/dist/src/inspection/validateCatalogPlanning.d.ts +21 -0
- package/dist/src/inspection/validateCatalogPlanning.js +187 -0
- package/dist/src/integrations/ActivityTrackerIntegration.d.ts +86 -0
- package/dist/src/integrations/ActivityTrackerIntegration.js +134 -0
- package/dist/src/integrations/ActivixGraphRunIntegration.d.ts +34 -0
- package/dist/src/integrations/ActivixGraphRunIntegration.js +338 -0
- package/dist/src/integrations/ActivixNodeActivityIntegration.d.ts +33 -0
- package/dist/src/integrations/ActivixNodeActivityIntegration.js +220 -0
- package/dist/src/integrations/cataloxGraphCatalog.d.ts +21 -0
- package/dist/src/integrations/cataloxGraphCatalog.js +30 -0
- package/dist/src/integrations/createActivixExellixIntegration.d.ts +14 -0
- package/dist/src/integrations/createActivixExellixIntegration.js +16 -0
- package/dist/src/integrations/createActivixFromEnv.d.ts +31 -0
- package/dist/src/integrations/createActivixFromEnv.js +53 -0
- package/dist/src/loaders/FileGraphLoader.d.ts +23 -0
- package/dist/src/loaders/FileGraphLoader.js +31 -0
- package/dist/src/playground/PlaygroundReporter.d.ts +40 -0
- package/dist/src/playground/PlaygroundReporter.js +480 -0
- package/dist/src/playground/index.d.ts +1 -0
- package/dist/src/playground/index.js +1 -0
- package/dist/src/runtime/ExellixGraphRuntime.d.ts +263 -0
- package/dist/src/runtime/ExellixGraphRuntime.js +1716 -0
- package/dist/src/runtime/GraphEngine.d.ts +33 -0
- package/dist/src/runtime/GraphEngine.js +4 -0
- package/dist/src/runtime/aiTasksObservability.d.ts +6 -0
- package/dist/src/runtime/aiTasksObservability.js +37 -0
- package/dist/src/runtime/aiTasksStrategyPhases.d.ts +46 -0
- package/dist/src/runtime/aiTasksStrategyPhases.js +93 -0
- package/dist/src/runtime/applyAiTaskProfileWebScopingToNarrix.d.ts +17 -0
- package/dist/src/runtime/applyAiTaskProfileWebScopingToNarrix.js +46 -0
- package/dist/src/runtime/buildAiTasksRunTaskRequest.d.ts +67 -0
- package/dist/src/runtime/buildAiTasksRunTaskRequest.js +164 -0
- package/dist/src/runtime/buildRunLog.d.ts +27 -0
- package/dist/src/runtime/buildRunLog.js +234 -0
- package/dist/src/runtime/buildRunTaskTaskConfigurationForward.d.ts +9 -0
- package/dist/src/runtime/buildRunTaskTaskConfigurationForward.js +80 -0
- package/dist/src/runtime/buildTaskNodeJobContext.d.ts +11 -0
- package/dist/src/runtime/buildTaskNodeJobContext.js +30 -0
- package/dist/src/runtime/canonicalModelUsed.d.ts +6 -0
- package/dist/src/runtime/canonicalModelUsed.js +36 -0
- package/dist/src/runtime/contextualScope.d.ts +7 -0
- package/dist/src/runtime/contextualScope.js +121 -0
- package/dist/src/runtime/dataFiltersEvaluation.d.ts +60 -0
- package/dist/src/runtime/dataFiltersEvaluation.js +169 -0
- package/dist/src/runtime/deepMerge.d.ts +5 -0
- package/dist/src/runtime/deepMerge.js +22 -0
- package/dist/src/runtime/events.d.ts +92 -0
- package/dist/src/runtime/events.js +122 -0
- package/dist/src/runtime/executionMatrixHost.d.ts +98 -0
- package/dist/src/runtime/executionMatrixHost.js +134 -0
- package/dist/src/runtime/executionVariableBuckets.d.ts +67 -0
- package/dist/src/runtime/executionVariableBuckets.js +96 -0
- package/dist/src/runtime/finalizers/errors.d.ts +9 -0
- package/dist/src/runtime/finalizers/errors.js +10 -0
- package/dist/src/runtime/finalizers/executeFinalizer.d.ts +40 -0
- package/dist/src/runtime/finalizers/executeFinalizer.js +471 -0
- package/dist/src/runtime/finalizers/schema.d.ts +18 -0
- package/dist/src/runtime/finalizers/schema.js +63 -0
- package/dist/src/runtime/finalizers/validateFinalizer.d.ts +16 -0
- package/dist/src/runtime/finalizers/validateFinalizer.js +534 -0
- package/dist/src/runtime/graphDocumentFingerprint.d.ts +8 -0
- package/dist/src/runtime/graphDocumentFingerprint.js +21 -0
- package/dist/src/runtime/graphEngineMemoryPaths.d.ts +12 -0
- package/dist/src/runtime/graphEngineMemoryPaths.js +55 -0
- package/dist/src/runtime/graphResponseMapping.d.ts +23 -0
- package/dist/src/runtime/graphResponseMapping.js +156 -0
- package/dist/src/runtime/graphResponseMigration.d.ts +7 -0
- package/dist/src/runtime/graphResponseMigration.js +44 -0
- package/dist/src/runtime/graphRunExecutionSeed.d.ts +29 -0
- package/dist/src/runtime/graphRunExecutionSeed.js +61 -0
- package/dist/src/runtime/graphRunIdentity.d.ts +7 -0
- package/dist/src/runtime/graphRunIdentity.js +18 -0
- package/dist/src/runtime/localSkills/deterministicRule.d.ts +137 -0
- package/dist/src/runtime/localSkills/deterministicRule.js +196 -0
- package/dist/src/runtime/localSkills/index.d.ts +12 -0
- package/dist/src/runtime/localSkills/index.js +14 -0
- package/dist/src/runtime/localSkills/memorixItemToScopedOutput.d.ts +7 -0
- package/dist/src/runtime/localSkills/memorixItemToScopedOutput.js +104 -0
- package/dist/src/runtime/localSkills/memorixRuntime.d.ts +9 -0
- package/dist/src/runtime/localSkills/memorixRuntime.js +70 -0
- package/dist/src/runtime/localSkills/memorixScopedConfig.d.ts +16 -0
- package/dist/src/runtime/localSkills/memorixScopedConfig.js +18 -0
- package/dist/src/runtime/localSkills/scopedAnswerAssembler.d.ts +23 -0
- package/dist/src/runtime/localSkills/scopedAnswerAssembler.js +35 -0
- package/dist/src/runtime/localSkills/scopedAnswerFields.d.ts +12 -0
- package/dist/src/runtime/localSkills/scopedAnswerFields.js +66 -0
- package/dist/src/runtime/localSkills/scopedAnswerWriter.d.ts +32 -0
- package/dist/src/runtime/localSkills/scopedAnswerWriter.js +156 -0
- package/dist/src/runtime/localSkills/scopedDataReader.d.ts +47 -0
- package/dist/src/runtime/localSkills/scopedDataReader.js +89 -0
- package/dist/src/runtime/localSkills/utils.d.ts +12 -0
- package/dist/src/runtime/localSkills/utils.js +39 -0
- package/dist/src/runtime/materializeStructuredRunTaskInput.d.ts +9 -0
- package/dist/src/runtime/materializeStructuredRunTaskInput.js +34 -0
- package/dist/src/runtime/memory.d.ts +51 -0
- package/dist/src/runtime/memory.js +250 -0
- package/dist/src/runtime/mergeExellixGraphRuntimeInvocation.d.ts +18 -0
- package/dist/src/runtime/mergeExellixGraphRuntimeInvocation.js +32 -0
- package/dist/src/runtime/modelConfigSelection.d.ts +7 -0
- package/dist/src/runtime/modelConfigSelection.js +37 -0
- package/dist/src/runtime/narrixIngestEnv.d.ts +9 -0
- package/dist/src/runtime/narrixIngestEnv.js +18 -0
- package/dist/src/runtime/pathExpr.d.ts +36 -0
- package/dist/src/runtime/pathExpr.js +131 -0
- package/dist/src/runtime/predicates.d.ts +14 -0
- package/dist/src/runtime/predicates.js +86 -0
- package/dist/src/runtime/readTaskNodeInputsConfig.d.ts +23 -0
- package/dist/src/runtime/readTaskNodeInputsConfig.js +27 -0
- package/dist/src/runtime/resolveExecutionPipelineForTaskNode.d.ts +11 -0
- package/dist/src/runtime/resolveExecutionPipelineForTaskNode.js +93 -0
- package/dist/src/runtime/resolveGraphEngineMemoryPaths.d.ts +63 -0
- package/dist/src/runtime/resolveGraphEngineMemoryPaths.js +213 -0
- package/dist/src/runtime/resolveModelConfigForNode.d.ts +20 -0
- package/dist/src/runtime/resolveModelConfigForNode.js +69 -0
- package/dist/src/runtime/resolveNarrixForTaskNode.d.ts +14 -0
- package/dist/src/runtime/resolveNarrixForTaskNode.js +19 -0
- package/dist/src/runtime/resolveTaskKey.d.ts +11 -0
- package/dist/src/runtime/resolveTaskKey.js +28 -0
- package/dist/src/runtime/resolveTaskNodeInputs.d.ts +25 -0
- package/dist/src/runtime/resolveTaskNodeInputs.js +140 -0
- package/dist/src/runtime/runTaskAugments.d.ts +17 -0
- package/dist/src/runtime/runTaskAugments.js +37 -0
- package/dist/src/runtime/runTaskResponse.d.ts +4 -0
- package/dist/src/runtime/runTaskResponse.js +13 -0
- package/dist/src/runtime/runtimeObjects.d.ts +85 -0
- package/dist/src/runtime/runtimeObjects.js +50 -0
- package/dist/src/runtime/smartInputPaths.d.ts +13 -0
- package/dist/src/runtime/smartInputPaths.js +38 -0
- package/dist/src/runtime/stepRetry.d.ts +21 -0
- package/dist/src/runtime/stepRetry.js +238 -0
- package/dist/src/runtime/synthesizedContextPipeline.d.ts +12 -0
- package/dist/src/runtime/synthesizedContextPipeline.js +28 -0
- package/dist/src/runtime/taskNodeConditionsEvaluation.d.ts +27 -0
- package/dist/src/runtime/taskNodeConditionsEvaluation.js +140 -0
- package/dist/src/runtime/taskNodeMainReadiness.d.ts +45 -0
- package/dist/src/runtime/taskNodeMainReadiness.js +164 -0
- package/dist/src/runtime/taskNodeRunTaskPreflight.d.ts +89 -0
- package/dist/src/runtime/taskNodeRunTaskPreflight.js +204 -0
- package/dist/src/runtime/validateCanonicalGraphDocument.d.ts +25 -0
- package/dist/src/runtime/validateCanonicalGraphDocument.js +567 -0
- package/dist/src/runtime/variables.d.ts +2 -0
- package/dist/src/runtime/variables.js +1 -0
- package/dist/src/runtime/withTimeout.d.ts +5 -0
- package/dist/src/runtime/withTimeout.js +20 -0
- package/dist/src/types/aiTaskProfile.d.ts +41 -0
- package/dist/src/types/aiTaskProfile.js +6 -0
- package/dist/src/types/aiTasksDerivedTypes.d.ts +5 -0
- package/dist/src/types/aiTasksDerivedTypes.js +1 -0
- package/dist/src/types/events.d.ts +23 -0
- package/dist/src/types/events.js +1 -0
- package/dist/src/types/job.d.ts +9 -0
- package/dist/src/types/job.js +1 -0
- package/dist/src/types/narrix.d.ts +60 -0
- package/dist/src/types/narrix.js +1 -0
- package/dist/src/types/options.d.ts +122 -0
- package/dist/src/types/options.js +1 -0
- package/dist/src/types/refs.d.ts +747 -0
- package/dist/src/types/refs.js +12 -0
- package/dist/src/types/results.d.ts +103 -0
- package/dist/src/types/results.js +1 -0
- package/dist/src/types/runLog.d.ts +72 -0
- package/dist/src/types/runLog.js +18 -0
- package/dist/src/types/taskNodeConfiguration.d.ts +95 -0
- package/dist/src/types/taskNodeConfiguration.js +3 -0
- package/dist/src/util/packageVersion.d.ts +2 -0
- package/dist/src/util/packageVersion.js +12 -0
- package/dist/testkit/RealTasksClient.d.ts +16 -0
- package/dist/testkit/RealTasksClient.js +143 -0
- package/dist/testkit/depGraphEngineFactory.d.ts +6 -0
- package/dist/testkit/depGraphEngineFactory.js +54 -0
- package/dist/testkit/exellixRuntimeObjects.d.ts +7 -0
- package/dist/testkit/exellixRuntimeObjects.js +25 -0
- package/dist/testkit/inMemoryGraphLoader.d.ts +6 -0
- package/dist/testkit/inMemoryGraphLoader.js +12 -0
- package/dist/testkit/index.d.ts +4 -0
- package/dist/testkit/index.js +4 -0
- package/package.json +70 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { EXELLIX_STRUCTURED_DATA_FILTERS_V1 } from '../types/refs.js';
|
|
2
|
+
import { ExellixGraphError } from '../errors/ExellixGraphError.js';
|
|
3
|
+
import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
|
|
4
|
+
import { EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY } from '../inspection/types.js';
|
|
5
|
+
import { getStructuredDataFilterPathViolations } from './dataFiltersEvaluation.js';
|
|
6
|
+
import { conditionWhenSignature, countDefaultModelConfigCases, isEmptyConditionWhen, isGraphAiModelConfig, isModelConfigSelection, } from './modelConfigSelection.js';
|
|
7
|
+
/** Top-level keys permitted on a canonical exellix-graph executable graph JSON document. */
|
|
8
|
+
export const CANONICAL_GRAPH_TOP_LEVEL_KEYS = [
|
|
9
|
+
'id',
|
|
10
|
+
'version',
|
|
11
|
+
'modelConfig',
|
|
12
|
+
'jobKnowledge',
|
|
13
|
+
'nodes',
|
|
14
|
+
'edges',
|
|
15
|
+
'variables',
|
|
16
|
+
'response',
|
|
17
|
+
'metadata',
|
|
18
|
+
];
|
|
19
|
+
const allowed = new Set(CANONICAL_GRAPH_TOP_LEVEL_KEYS);
|
|
20
|
+
const MODEL_RUNTIME_FIELD_KEYS = new Set(['input', 'jobMemory', 'taskMemory', 'executionMemory', 'outputsMemory', 'aliasConfig']);
|
|
21
|
+
const METADATA_FORBIDDEN_FIELD_TARGETS = {
|
|
22
|
+
modelConfig: 'graph.modelConfig',
|
|
23
|
+
aliasConfig: 'runtime.aliasConfig',
|
|
24
|
+
jobKnowledge: 'graph.jobKnowledge',
|
|
25
|
+
taskKnowledge: 'node.taskKnowledge',
|
|
26
|
+
graphResponse: 'graph.response',
|
|
27
|
+
jobMemory: 'runtime.jobMemory',
|
|
28
|
+
taskMemory: 'runtime.taskMemory',
|
|
29
|
+
executionMemory: 'runtime.executionMemory',
|
|
30
|
+
outputsMemory: 'runtime.outputsMemory',
|
|
31
|
+
};
|
|
32
|
+
const FORBIDDEN_GRAPH_RESPONSE_KEYS = [
|
|
33
|
+
'version',
|
|
34
|
+
'target',
|
|
35
|
+
'primaryResponsePaths',
|
|
36
|
+
'debugResponsePaths',
|
|
37
|
+
'notableExecutionPaths',
|
|
38
|
+
'mappingPreset',
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Returns top-level keys on `graph` that are not part of the canonical executable document contract.
|
|
42
|
+
*/
|
|
43
|
+
export function getCanonicalGraphDocumentViolations(graph) {
|
|
44
|
+
if (graph == null || typeof graph !== 'object' || Array.isArray(graph)) {
|
|
45
|
+
return ['<not_a_plain_object>'];
|
|
46
|
+
}
|
|
47
|
+
const bad = [];
|
|
48
|
+
for (const key of Object.keys(graph)) {
|
|
49
|
+
if (!allowed.has(key))
|
|
50
|
+
bad.push(key);
|
|
51
|
+
}
|
|
52
|
+
return bad;
|
|
53
|
+
}
|
|
54
|
+
/** Pure-metadata keys permitted on task nodes (`metadata`). Execution config uses `taskConfiguration`. */
|
|
55
|
+
const TASK_NODE_PURE_METADATA_KEYS = new Set([
|
|
56
|
+
'graphReadability',
|
|
57
|
+
'catalogBinding',
|
|
58
|
+
'catalogRequest',
|
|
59
|
+
'name',
|
|
60
|
+
'description',
|
|
61
|
+
'exellixContractTarget',
|
|
62
|
+
EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY,
|
|
63
|
+
]);
|
|
64
|
+
/** Hints when authors still place execution keys under `metadata`. */
|
|
65
|
+
const LEGACY_METADATA_TO_TASK_CONFIGURATION = {
|
|
66
|
+
synthesizedContext: 'taskConfiguration.aiTaskProfile.inputSynthesis',
|
|
67
|
+
executionStrategyKey: 'taskConfiguration.executionStrategies',
|
|
68
|
+
outputConstraints: 'taskConfiguration.aiTasksOutputValidation',
|
|
69
|
+
narrix: 'taskConfiguration.narrix',
|
|
70
|
+
aiTaskProfile: 'taskConfiguration.aiTaskProfile',
|
|
71
|
+
narrixMode: 'taskConfiguration.narrixMode',
|
|
72
|
+
inputStrategyKey: 'taskConfiguration.inputStrategyKey',
|
|
73
|
+
narrixInput: 'taskConfiguration.narrixInput',
|
|
74
|
+
executionStrategies: 'taskConfiguration.executionStrategies',
|
|
75
|
+
aiTasksOutputValidation: 'taskConfiguration.aiTasksOutputValidation',
|
|
76
|
+
synthesizedInput: 'taskConfiguration.synthesizedInput',
|
|
77
|
+
modelConfig: 'taskConfiguration.modelConfig',
|
|
78
|
+
aliasConfig: 'runtime.aliasConfig',
|
|
79
|
+
llmCall: 'taskConfiguration.llmCall',
|
|
80
|
+
stepRetryPolicy: 'taskConfiguration.stepRetryPolicy',
|
|
81
|
+
concurrency: 'taskConfiguration.concurrency',
|
|
82
|
+
taskKind: 'taskConfiguration.taskKind',
|
|
83
|
+
autoValidateDecisionOutput: 'taskConfiguration.autoValidateDecisionOutput',
|
|
84
|
+
identity: 'taskConfiguration.identity',
|
|
85
|
+
runTaskIdentity: 'taskConfiguration.runTaskIdentity',
|
|
86
|
+
memoryKey: 'taskConfiguration.memoryKey',
|
|
87
|
+
taskTypeId: 'taskConfiguration.taskTypeId',
|
|
88
|
+
scopingMapId: 'taskConfiguration.scopingMapId',
|
|
89
|
+
entityIdPath: 'taskConfiguration.entityIdPath',
|
|
90
|
+
rules: 'taskConfiguration.rules',
|
|
91
|
+
taskKnowledge: 'node.taskKnowledge',
|
|
92
|
+
jobKnowledge: 'graph.jobKnowledge',
|
|
93
|
+
jobMemory: 'runtime.jobMemory',
|
|
94
|
+
taskMemory: 'runtime.taskMemory',
|
|
95
|
+
executionMemory: 'runtime.executionMemory',
|
|
96
|
+
outputsMemory: 'runtime.outputsMemory',
|
|
97
|
+
};
|
|
98
|
+
/** Web-scope keys are not allowed under `taskConfiguration.narrix` (use `taskConfiguration.aiTaskProfile.webScoping`). */
|
|
99
|
+
const FORBIDDEN_NARRIX_WEB_KEYS = [
|
|
100
|
+
'enableWebScope',
|
|
101
|
+
'forceWebScope',
|
|
102
|
+
'webScopeQuestions',
|
|
103
|
+
'webScoping',
|
|
104
|
+
'webScopeTemplates',
|
|
105
|
+
'webScopeQuestionTemplate',
|
|
106
|
+
'webScopeObjects',
|
|
107
|
+
'webScopeEntityIdPath',
|
|
108
|
+
'webScopeEntityTypePath',
|
|
109
|
+
'enrichWebScopeQuestionFromExecutionRaw',
|
|
110
|
+
];
|
|
111
|
+
function isPlainObject(v) {
|
|
112
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
113
|
+
}
|
|
114
|
+
function assertOptionalStringArray(value, path, context,
|
|
115
|
+
/** Shown in the error message after "non-empty strings"; default describes taskKnowledge refs. */
|
|
116
|
+
arrayRole) {
|
|
117
|
+
if (value === undefined)
|
|
118
|
+
return;
|
|
119
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.trim() === '')) {
|
|
120
|
+
const role = arrayRole ?? 'knowledge references';
|
|
121
|
+
throw new ExellixGraphError(context?.nodeId
|
|
122
|
+
? ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE
|
|
123
|
+
: ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path} must be an array of non-empty strings (${role}).`, context);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const TASK_NODE_CONDITIONS_KEYS = new Set([
|
|
127
|
+
'narratives',
|
|
128
|
+
'dataFilters',
|
|
129
|
+
'jsonConditions',
|
|
130
|
+
'jsConditionFunction',
|
|
131
|
+
'aiCondition',
|
|
132
|
+
]);
|
|
133
|
+
const TASK_NODE_CONDITION_WHEN_KEYS = new Set([
|
|
134
|
+
'jsonConditions',
|
|
135
|
+
'jsConditionFunction',
|
|
136
|
+
'aiCondition',
|
|
137
|
+
]);
|
|
138
|
+
const SCOPE_FILTER_WHERE_KEYS = new Set(['path', 'eq', 'eqAny']);
|
|
139
|
+
function assertOptionalConditionsDataFiltersValue(value, path, context) {
|
|
140
|
+
if (value === undefined)
|
|
141
|
+
return;
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
assertOptionalStringArray(value, path, context, 'data filter ids');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!isPlainObject(value)) {
|
|
147
|
+
throw new ExellixGraphError(context?.nodeId
|
|
148
|
+
? ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE
|
|
149
|
+
: ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path} must be string[] (legacy ids), or structured { version: "${EXELLIX_STRUCTURED_DATA_FILTERS_V1}", when: predicate }.`, context);
|
|
150
|
+
}
|
|
151
|
+
if (value.version !== EXELLIX_STRUCTURED_DATA_FILTERS_V1) {
|
|
152
|
+
throw new ExellixGraphError(context?.nodeId
|
|
153
|
+
? ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE
|
|
154
|
+
: ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.version must be "${EXELLIX_STRUCTURED_DATA_FILTERS_V1}" when structured dataFilters are used.`, { ...context, actualVersion: value.version });
|
|
155
|
+
}
|
|
156
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'when')) {
|
|
157
|
+
throw new ExellixGraphError(context?.nodeId
|
|
158
|
+
? ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE
|
|
159
|
+
: ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.when is required for structured dataFilters.`, context);
|
|
160
|
+
}
|
|
161
|
+
const when = value.when;
|
|
162
|
+
if (when == null || typeof when !== 'object' || Array.isArray(when)) {
|
|
163
|
+
throw new ExellixGraphError(context?.nodeId
|
|
164
|
+
? ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE
|
|
165
|
+
: ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.when must be a graph predicate object.`, context);
|
|
166
|
+
}
|
|
167
|
+
const badPaths = getStructuredDataFilterPathViolations(when);
|
|
168
|
+
if (badPaths.length > 0) {
|
|
169
|
+
throw new ExellixGraphError(context?.nodeId
|
|
170
|
+
? ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE
|
|
171
|
+
: ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.when uses paths outside the data record contract (only \`data\` and \`data.*\` are allowed). Offending paths: ${badPaths.join(', ')}.`, { ...context, badPaths });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function assertOptionalBoolean(value, path, ctx) {
|
|
175
|
+
if (value === undefined)
|
|
176
|
+
return;
|
|
177
|
+
if (typeof value !== 'boolean') {
|
|
178
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path} must be a boolean when present.`, ctx);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function assertTaskNodeJsonCondition(value, path, ctx) {
|
|
182
|
+
if (value === undefined)
|
|
183
|
+
return;
|
|
184
|
+
if (!isPlainObject(value)) {
|
|
185
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path} must be a plain object.`, ctx);
|
|
186
|
+
}
|
|
187
|
+
if (value.condition === undefined) {
|
|
188
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.condition is required.`, ctx);
|
|
189
|
+
}
|
|
190
|
+
assertOptionalBoolean(value.negate, `${path}.negate`, ctx);
|
|
191
|
+
if (value.parameters !== undefined && !isPlainObject(value.parameters)) {
|
|
192
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.parameters must be a plain object when present.`, ctx);
|
|
193
|
+
}
|
|
194
|
+
if (value.options !== undefined && !isPlainObject(value.options)) {
|
|
195
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.options must be a plain object when present.`, ctx);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function assertTaskNodeJsCondition(value, path, ctx) {
|
|
199
|
+
if (value === undefined)
|
|
200
|
+
return;
|
|
201
|
+
if (!isPlainObject(value)) {
|
|
202
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path} must be a plain object.`, ctx);
|
|
203
|
+
}
|
|
204
|
+
if (typeof value.conditionFunctionId !== 'string' || value.conditionFunctionId.trim() === '') {
|
|
205
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.conditionFunctionId must be a non-empty string.`, ctx);
|
|
206
|
+
}
|
|
207
|
+
assertOptionalBoolean(value.negate, `${path}.negate`, ctx);
|
|
208
|
+
if (value.parameters !== undefined && !isPlainObject(value.parameters)) {
|
|
209
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.parameters must be a plain object when present.`, ctx);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function assertTaskNodeAiCondition(value, path, ctx) {
|
|
213
|
+
if (value === undefined)
|
|
214
|
+
return;
|
|
215
|
+
if (!isPlainObject(value)) {
|
|
216
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path} must be a plain object.`, ctx);
|
|
217
|
+
}
|
|
218
|
+
if (typeof value.condition !== 'string' || value.condition.trim() === '') {
|
|
219
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.condition must be a non-empty string.`, ctx);
|
|
220
|
+
}
|
|
221
|
+
assertOptionalBoolean(value.negate, `${path}.negate`, ctx);
|
|
222
|
+
if (value.mode !== undefined && value.mode !== 'hybrid' && value.mode !== 'model') {
|
|
223
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.mode must be "hybrid" or "model" when present.`, ctx);
|
|
224
|
+
}
|
|
225
|
+
if (value.model !== undefined && typeof value.model !== 'string') {
|
|
226
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.model must be a string when present.`, ctx);
|
|
227
|
+
}
|
|
228
|
+
if (value.parameters !== undefined && !isPlainObject(value.parameters)) {
|
|
229
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}.parameters must be a plain object when present.`, ctx);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function assertTaskNodeConditionWhen(value, path, ctx, options) {
|
|
233
|
+
if (value === undefined)
|
|
234
|
+
return;
|
|
235
|
+
if (!isPlainObject(value)) {
|
|
236
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path} must be a plain object when present.`, ctx);
|
|
237
|
+
}
|
|
238
|
+
for (const key of Object.keys(value)) {
|
|
239
|
+
if (!TASK_NODE_CONDITION_WHEN_KEYS.has(key)) {
|
|
240
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path}: unknown key \`${key}\` — only jsonConditions, jsConditionFunction, and aiCondition are allowed.`, { ...ctx, whenKey: key });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (options?.requireNonEmpty && isEmptyConditionWhen(value)) {
|
|
244
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path} must include at least one of jsonConditions, jsConditionFunction, or aiCondition.`, ctx);
|
|
245
|
+
}
|
|
246
|
+
assertTaskNodeJsonCondition(value.jsonConditions, `${path}.jsonConditions`, ctx);
|
|
247
|
+
assertTaskNodeJsCondition(value.jsConditionFunction, `${path}.jsConditionFunction`, ctx);
|
|
248
|
+
assertTaskNodeAiCondition(value.aiCondition, `${path}.aiCondition`, ctx);
|
|
249
|
+
}
|
|
250
|
+
function assertOptionalTaskNodeConditions(value, nodeId, context) {
|
|
251
|
+
if (value === undefined)
|
|
252
|
+
return;
|
|
253
|
+
if (!isPlainObject(value)) {
|
|
254
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${nodeId}": conditions must be a plain object when present.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(nodeId) });
|
|
255
|
+
}
|
|
256
|
+
for (const key of Object.keys(value)) {
|
|
257
|
+
if (!TASK_NODE_CONDITIONS_KEYS.has(key)) {
|
|
258
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${nodeId}": unknown key \`conditions.${key}\`.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(nodeId), conditionsKey: key });
|
|
259
|
+
}
|
|
260
|
+
if (key === 'aiFilter') {
|
|
261
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${nodeId}": conditions.aiFilter was removed — use conditions.aiCondition.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(nodeId) });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const ctx = { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(nodeId) };
|
|
265
|
+
assertOptionalStringArray(value.narratives, `Task node "${nodeId}": conditions.narratives`, ctx, 'narrative ids');
|
|
266
|
+
if (value.dataFilters !== undefined) {
|
|
267
|
+
if (!Array.isArray(value.dataFilters) || !value.dataFilters.every((x) => typeof x === 'string' && x.trim() !== '')) {
|
|
268
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${nodeId}": conditions.dataFilters must be string[] (catalog ids). Structured exellix.dataFilters.v1 is not supported on task nodes — use conditions.jsonConditions.`, ctx);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
assertTaskNodeJsonCondition(value.jsonConditions, `Task node "${nodeId}": conditions.jsonConditions`, ctx);
|
|
272
|
+
assertTaskNodeJsCondition(value.jsConditionFunction, `Task node "${nodeId}": conditions.jsConditionFunction`, ctx);
|
|
273
|
+
assertTaskNodeAiCondition(value.aiCondition, `Task node "${nodeId}": conditions.aiCondition`, ctx);
|
|
274
|
+
}
|
|
275
|
+
function assertModelConfigSelection(value, path, context) {
|
|
276
|
+
if (value === undefined)
|
|
277
|
+
return;
|
|
278
|
+
const code = context?.nodeId
|
|
279
|
+
? ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE
|
|
280
|
+
: ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT;
|
|
281
|
+
if (isGraphAiModelConfig(value)) {
|
|
282
|
+
throw new ExellixGraphError(code, `${path}: flat { xynthesisModel, skillModel } was removed. Use { cases: [{ modelConfig: { xynthesisModel, skillModel } }] } (one case with no \`when\` for a single default).`, context);
|
|
283
|
+
}
|
|
284
|
+
if (!isModelConfigSelection(value)) {
|
|
285
|
+
throw new ExellixGraphError(code, `${path} must be { cases: ModelConfigCase[] } where each case has modelConfig and optional when.`, context);
|
|
286
|
+
}
|
|
287
|
+
const cases = value.cases;
|
|
288
|
+
if (!Array.isArray(cases) || cases.length === 0) {
|
|
289
|
+
throw new ExellixGraphError(code, `${path}.cases must be a non-empty array.`, context);
|
|
290
|
+
}
|
|
291
|
+
const ctx = { jobId: context?.jobId, graphId: context?.graphId, nodeId: context?.nodeId };
|
|
292
|
+
const whenSigs = new Set();
|
|
293
|
+
for (let i = 0; i < cases.length; i++) {
|
|
294
|
+
const c = cases[i];
|
|
295
|
+
const casePath = `${path}.cases[${i}]`;
|
|
296
|
+
if (!isPlainObject(c)) {
|
|
297
|
+
throw new ExellixGraphError(code, `${casePath} must be a plain object.`, context);
|
|
298
|
+
}
|
|
299
|
+
if (!isGraphAiModelConfig(c.modelConfig)) {
|
|
300
|
+
throw new ExellixGraphError(code, `${casePath}.modelConfig must be { xynthesisModel: string, skillModel: string } with non-empty values.`, context);
|
|
301
|
+
}
|
|
302
|
+
if (isEmptyConditionWhen(c.when)) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
assertTaskNodeConditionWhen(c.when, `${casePath}.when`, ctx, { requireNonEmpty: true });
|
|
306
|
+
const sig = conditionWhenSignature(c.when);
|
|
307
|
+
if (whenSigs.has(sig)) {
|
|
308
|
+
throw new ExellixGraphError(code, `${path}: duplicate \`when\` condition — each conditioned case must be unique.`, { ...context, duplicateWhen: sig });
|
|
309
|
+
}
|
|
310
|
+
whenSigs.add(sig);
|
|
311
|
+
}
|
|
312
|
+
const defaultCount = countDefaultModelConfigCases(cases);
|
|
313
|
+
if (defaultCount !== 1) {
|
|
314
|
+
throw new ExellixGraphError(code, `${path}.cases must contain exactly one case with no \`when\` (the unconditional default); found ${defaultCount}.`, context);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/** Runtime host override: flat GraphAiModelConfig only. */
|
|
318
|
+
function assertOptionalRuntimeFlatModelConfig(value, path, context) {
|
|
319
|
+
if (value === undefined)
|
|
320
|
+
return;
|
|
321
|
+
if (!isGraphAiModelConfig(value)) {
|
|
322
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${path} must be exactly { xynthesisModel: string, skillModel: string } (runtime host override does not use cases).`, context);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function assertContextualKnowledgeScope(scope, nodeId, context) {
|
|
326
|
+
if (scope === undefined)
|
|
327
|
+
return;
|
|
328
|
+
if (!isPlainObject(scope))
|
|
329
|
+
return;
|
|
330
|
+
const items = scope.contextualKnowledge;
|
|
331
|
+
if (!Array.isArray(items))
|
|
332
|
+
return;
|
|
333
|
+
const ctx = { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(nodeId) };
|
|
334
|
+
for (let i = 0; i < items.length; i++) {
|
|
335
|
+
const item = items[i];
|
|
336
|
+
if (!isPlainObject(item))
|
|
337
|
+
continue;
|
|
338
|
+
const filter = item.filter;
|
|
339
|
+
if (!isPlainObject(filter))
|
|
340
|
+
continue;
|
|
341
|
+
const where = filter.where;
|
|
342
|
+
if (!Array.isArray(where))
|
|
343
|
+
continue;
|
|
344
|
+
for (let j = 0; j < where.length; j++) {
|
|
345
|
+
const leaf = where[j];
|
|
346
|
+
const leafPath = `Task node "${nodeId}": scope.contextualKnowledge[${i}].filter.where[${j}]`;
|
|
347
|
+
if (!isPlainObject(leaf)) {
|
|
348
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${leafPath} must be a plain object.`, ctx);
|
|
349
|
+
}
|
|
350
|
+
if (typeof leaf.path !== 'string' || leaf.path.trim() === '') {
|
|
351
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${leafPath}.path must be a non-empty string.`, ctx);
|
|
352
|
+
}
|
|
353
|
+
for (const key of Object.keys(leaf)) {
|
|
354
|
+
if (!SCOPE_FILTER_WHERE_KEYS.has(key)) {
|
|
355
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${leafPath}: unknown key \`${key}\` — only path, eq, and eqAny are allowed (cidrContains was removed).`, { ...ctx, scopeFilterKey: key });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const hasEq = Object.prototype.hasOwnProperty.call(leaf, 'eq');
|
|
359
|
+
const hasEqAny = Object.prototype.hasOwnProperty.call(leaf, 'eqAny');
|
|
360
|
+
if (!hasEq && !hasEqAny) {
|
|
361
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `${leafPath} must include eq or eqAny.`, ctx);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function assertGraphResponseDefinition(value, path, context) {
|
|
367
|
+
if (!isPlainObject(value)) {
|
|
368
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path} must be an object with optional missing and required shape fields.`, context);
|
|
369
|
+
}
|
|
370
|
+
for (const key of FORBIDDEN_GRAPH_RESPONSE_KEYS) {
|
|
371
|
+
if (key in value) {
|
|
372
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.${key} is not part of the root response contract. Move only missing and shape to graph.response; keep editor-only fields outside the executable graph contract.`, { ...context, responseKey: key });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (value.missing !== undefined && value.missing !== 'omit' && value.missing !== 'null') {
|
|
376
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.missing must be "omit" or "null" when provided.`, { ...context, actual: value.missing });
|
|
377
|
+
}
|
|
378
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'shape')) {
|
|
379
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.shape is required.`, context);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function findFinalizerType(node) {
|
|
383
|
+
if (!isPlainObject(node))
|
|
384
|
+
return undefined;
|
|
385
|
+
return typeof node.finalizerType === 'string' ? node.finalizerType : undefined;
|
|
386
|
+
}
|
|
387
|
+
function isTaskShape(node) {
|
|
388
|
+
if (!isPlainObject(node))
|
|
389
|
+
return false;
|
|
390
|
+
if (node.type === 'finalizer')
|
|
391
|
+
return false;
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
/** Single-node validation. Throws `NON_CANONICAL_TASK_NODE` on the first canonical violation. */
|
|
395
|
+
export function assertCanonicalTaskNode(node, context) {
|
|
396
|
+
if (!isPlainObject(node)) {
|
|
397
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Graph node at index ${context?.nodeIndex ?? '?'} is not a plain object.`, { jobId: context?.jobId, graphId: context?.graphId });
|
|
398
|
+
}
|
|
399
|
+
if (typeof node.id !== 'string' || node.id.length === 0) {
|
|
400
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Graph node at index ${context?.nodeIndex ?? '?'} is missing a string id.`, { jobId: context?.jobId, graphId: context?.graphId });
|
|
401
|
+
}
|
|
402
|
+
assertOptionalStringArray(node.taskKnowledge, `Task node "${String(node.id)}": taskKnowledge`, {
|
|
403
|
+
jobId: context?.jobId,
|
|
404
|
+
graphId: context?.graphId,
|
|
405
|
+
nodeId: String(node.id),
|
|
406
|
+
});
|
|
407
|
+
// Finalizer nodes only need to declare type=finalizer + finalizerType; nothing else is gated here.
|
|
408
|
+
if (node.type === 'finalizer') {
|
|
409
|
+
if (typeof findFinalizerType(node) !== 'string') {
|
|
410
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Finalizer node "${String(node.id)}" must declare a string finalizerType.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (!isTaskShape(node))
|
|
415
|
+
return;
|
|
416
|
+
if ('outputMapping' in node) {
|
|
417
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": outputMapping belongs on the finalizer. Use executionMapping to write task results into executionMemory.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
418
|
+
}
|
|
419
|
+
assertOptionalTaskNodeConditions(node.conditions, String(node.id), {
|
|
420
|
+
jobId: context?.jobId,
|
|
421
|
+
graphId: context?.graphId,
|
|
422
|
+
nodeId: String(node.id),
|
|
423
|
+
});
|
|
424
|
+
// skillKey: only on node.skillKey
|
|
425
|
+
const data = isPlainObject(node.data) ? node.data : undefined;
|
|
426
|
+
const meta = isPlainObject(node.metadata)
|
|
427
|
+
? node.metadata
|
|
428
|
+
: undefined;
|
|
429
|
+
if (data && 'skillKey' in data) {
|
|
430
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": skillKey must be on node.skillKey, not node.data.skillKey.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
431
|
+
}
|
|
432
|
+
if (meta && 'skillKey' in meta) {
|
|
433
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": skillKey must be on node.skillKey, not node.metadata.skillKey.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
434
|
+
}
|
|
435
|
+
// variables: only on node.variables
|
|
436
|
+
if (data && 'variables' in data) {
|
|
437
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": node-level variables must be on node.variables, not node.data.variables.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
438
|
+
}
|
|
439
|
+
if (meta && 'variables' in meta) {
|
|
440
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": node-level variables must be on node.variables, not node.metadata.variables.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
441
|
+
}
|
|
442
|
+
const taskNode = node;
|
|
443
|
+
const tc = isPlainObject(taskNode.taskConfiguration)
|
|
444
|
+
? taskNode.taskConfiguration
|
|
445
|
+
: undefined;
|
|
446
|
+
assertModelConfigSelection(tc?.modelConfig, `Task node "${String(node.id)}": taskConfiguration.modelConfig`, {
|
|
447
|
+
jobId: context?.jobId,
|
|
448
|
+
graphId: context?.graphId,
|
|
449
|
+
nodeId: String(node.id),
|
|
450
|
+
});
|
|
451
|
+
assertContextualKnowledgeScope(taskNode.scope, String(node.id), {
|
|
452
|
+
jobId: context?.jobId,
|
|
453
|
+
graphId: context?.graphId,
|
|
454
|
+
nodeId: String(node.id),
|
|
455
|
+
});
|
|
456
|
+
if (meta) {
|
|
457
|
+
for (const key of Object.keys(meta)) {
|
|
458
|
+
if (!TASK_NODE_PURE_METADATA_KEYS.has(key)) {
|
|
459
|
+
const redirect = LEGACY_METADATA_TO_TASK_CONFIGURATION[key];
|
|
460
|
+
const suffix = redirect
|
|
461
|
+
? ` Move this to \`${redirect}\`.`
|
|
462
|
+
: ` Execution-related keys belong on taskConfiguration or node.variables — metadata is allowlisted to pure planning/description fields only.`;
|
|
463
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": forbidden key \`metadata.${key}\`.${suffix}`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id), metadataKey: key });
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const narrixTc = tc && isPlainObject(tc.narrix) ? tc.narrix : undefined;
|
|
468
|
+
if (narrixTc) {
|
|
469
|
+
for (const k of FORBIDDEN_NARRIX_WEB_KEYS) {
|
|
470
|
+
if (k in narrixTc) {
|
|
471
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": taskConfiguration.narrix.${k} is forbidden — author web scope under taskConfiguration.aiTaskProfile.webScoping.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id), narrixWebKey: k });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const profile = tc && isPlainObject(tc.aiTaskProfile) ? tc.aiTaskProfile : undefined;
|
|
476
|
+
const inputSynthesis = profile && isPlainObject(profile.inputSynthesis) ? profile.inputSynthesis : undefined;
|
|
477
|
+
if (inputSynthesis && 'alsoWriteLegacySynthesizedContext' in inputSynthesis) {
|
|
478
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": taskConfiguration.aiTaskProfile.inputSynthesis.alsoWriteLegacySynthesizedContext is removed in 5.x.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
479
|
+
}
|
|
480
|
+
if (inputSynthesis?.enabled === true &&
|
|
481
|
+
Array.isArray(taskNode.executionPipeline)) {
|
|
482
|
+
for (const step of taskNode.executionPipeline) {
|
|
483
|
+
if (step?.phase === 'pre' && step?.type === 'synthesized-context') {
|
|
484
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": inputSynthesis.enabled is true but executionPipeline already declares a PRE synthesized-context step. Drop the manual step — graph-engine emits it from taskConfiguration.aiTaskProfile.inputSynthesis.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Throws {@link ExellixGraphError} when the value is not a canonical graph document
|
|
491
|
+
* (forbidden top-level keys, record-keyed `nodes`, root `outputConstraints`, or any
|
|
492
|
+
* legacy task-node alias). Upstream producers must emit only model fields (`id`,
|
|
493
|
+
* optional `version`, `modelConfig`, `jobKnowledge`, `nodes`, `edges`,
|
|
494
|
+
* `variables`, required `response`, and `metadata`); runtime fields belong under the execution
|
|
495
|
+
* request `runtime` object.
|
|
496
|
+
*/
|
|
497
|
+
export function assertCanonicalGraphDocument(graph, context) {
|
|
498
|
+
const violations = getCanonicalGraphDocumentViolations(graph);
|
|
499
|
+
if (violations.length > 0) {
|
|
500
|
+
const graphId = context?.graphId ??
|
|
501
|
+
(graph != null && typeof graph === 'object' && !Array.isArray(graph) && typeof graph.id === 'string'
|
|
502
|
+
? graph.id
|
|
503
|
+
: undefined);
|
|
504
|
+
const keys = violations.join(', ');
|
|
505
|
+
const runtimeKeys = violations.filter((key) => MODEL_RUNTIME_FIELD_KEYS.has(key));
|
|
506
|
+
const runtimeSuffix = runtimeKeys.length
|
|
507
|
+
? ` Runtime state belongs under the execution request runtime object, not the graph model: ${runtimeKeys
|
|
508
|
+
.map((key) => `runtime.${key}`)
|
|
509
|
+
.join(', ')}.`
|
|
510
|
+
: '';
|
|
511
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `This is not a canonical exellix-graph executable graph document. Forbidden top-level key(s): ${keys}. ` +
|
|
512
|
+
`Only id, version, modelConfig, jobKnowledge, nodes, edges, variables, response, and metadata are allowed at the document root; ` +
|
|
513
|
+
`graph-document metadata (name, description, exellixContractTarget, graphEntry, catalogRequests, etc.) must live under metadata, while final response shaping belongs at graph.response. ` +
|
|
514
|
+
`The runtime does not normalize or relocate fields — fix the upstream serializer.` +
|
|
515
|
+
runtimeSuffix, {
|
|
516
|
+
jobId: context?.jobId,
|
|
517
|
+
graphId,
|
|
518
|
+
nonCanonicalTopLevelKeys: violations,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
const resolvedGraphId = context?.graphId ??
|
|
522
|
+
(graph != null && typeof graph === 'object' && typeof graph.id === 'string'
|
|
523
|
+
? graph.id
|
|
524
|
+
: undefined);
|
|
525
|
+
assertOptionalStringArray(graph.jobKnowledge, 'graph.jobKnowledge', {
|
|
526
|
+
jobId: context?.jobId,
|
|
527
|
+
graphId: resolvedGraphId,
|
|
528
|
+
});
|
|
529
|
+
assertModelConfigSelection(graph.modelConfig, 'graph.modelConfig', {
|
|
530
|
+
jobId: context?.jobId,
|
|
531
|
+
graphId: resolvedGraphId,
|
|
532
|
+
});
|
|
533
|
+
const metadata = isPlainObject(graph.metadata)
|
|
534
|
+
? graph.metadata
|
|
535
|
+
: undefined;
|
|
536
|
+
if (metadata) {
|
|
537
|
+
for (const key of Object.keys(metadata)) {
|
|
538
|
+
const target = METADATA_FORBIDDEN_FIELD_TARGETS[key];
|
|
539
|
+
if (target) {
|
|
540
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `Graph "${String(resolvedGraphId ?? '?')}": metadata.${key} is not part of the static metadata block. Move this field to \`${target}\`.`, { jobId: context?.jobId, graphId: resolvedGraphId, metadataKey: key });
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const graphEntry = metadata.graphEntry;
|
|
544
|
+
if (graphEntry != null && typeof graphEntry === 'object' && !Array.isArray(graphEntry)) {
|
|
545
|
+
const ge = graphEntry;
|
|
546
|
+
if (Object.prototype.hasOwnProperty.call(ge, 'dataFilters')) {
|
|
547
|
+
assertOptionalConditionsDataFiltersValue(ge.dataFilters, `Graph "${String(resolvedGraphId ?? '?')}": metadata.graphEntry.dataFilters`, { jobId: context?.jobId, graphId: resolvedGraphId });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// 5.x: graph.nodes must be an array.
|
|
552
|
+
if (!Array.isArray(graph.nodes)) {
|
|
553
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `Graph "${String(resolvedGraphId ?? '?')}" must declare nodes as an array. Record-keyed nodes (Record<string, GraphNode>) were removed in 5.x.`, { jobId: context?.jobId, graphId: resolvedGraphId });
|
|
554
|
+
}
|
|
555
|
+
// Per-node canonical checks.
|
|
556
|
+
const nodes = graph.nodes;
|
|
557
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
558
|
+
assertCanonicalTaskNode(nodes[i], { jobId: context?.jobId, graphId: resolvedGraphId, nodeIndex: i });
|
|
559
|
+
}
|
|
560
|
+
if (!Object.prototype.hasOwnProperty.call(graph, 'response')) {
|
|
561
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `Graph "${String(resolvedGraphId ?? '?')}" must declare root graph.response. Final response shaping must not live under metadata.graphResponse.responseMapping or finalizer output selection.`, { jobId: context?.jobId, graphId: resolvedGraphId });
|
|
562
|
+
}
|
|
563
|
+
assertGraphResponseDefinition(graph.response, 'graph.response', {
|
|
564
|
+
jobId: context?.jobId,
|
|
565
|
+
graphId: resolvedGraphId,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { readExecutionVariableBuckets, seedGraphVariableBucketsOnExecution, mirrorTaskVariablesOnExecution, seedGraphVariableBucketsFromRuntime, buildMemoryResolutionRootFromExecution, buildPredicateEvalContextForNode, } from './executionVariableBuckets.js';
|
|
2
|
+
export type { ExecutionVariableBuckets } from './executionVariableBuckets.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { readExecutionVariableBuckets, seedGraphVariableBucketsOnExecution, mirrorTaskVariablesOnExecution, seedGraphVariableBucketsFromRuntime, buildMemoryResolutionRootFromExecution, buildPredicateEvalContextForNode, } from './executionVariableBuckets.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rejects with `errorFactory()` if `promise` does not settle within `ms`.
|
|
3
|
+
* No-op when `ms` is not a positive finite number.
|
|
4
|
+
*/
|
|
5
|
+
export function withTimeout(promise, ms, errorFactory) {
|
|
6
|
+
if (!(ms > 0) || !Number.isFinite(ms))
|
|
7
|
+
return promise;
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const timer = setTimeout(() => {
|
|
10
|
+
reject(errorFactory());
|
|
11
|
+
}, ms);
|
|
12
|
+
promise.then((value) => {
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
resolve(value);
|
|
15
|
+
}, (err) => {
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
reject(err);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional task metadata aligned with `@x12i/graph-composer` authoring / `reportTaskNodeProtocolGaps`.
|
|
3
|
+
* Exellix-graph-engine merges {@link AiTaskProfileMetadata.webScoping} into `taskConfiguration.narrix` for execution
|
|
4
|
+
* when `enabled: true` and `questions` is non-empty (see `applyAiTaskProfileWebScopingToNarrix`).
|
|
5
|
+
*/
|
|
6
|
+
export type AiTaskProfileWebScoping = {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
/** When `enabled` is `true`, must be a non-empty array of non-empty strings (graph-composer contract). */
|
|
9
|
+
questions?: string[];
|
|
10
|
+
};
|
|
11
|
+
export type AiTaskProfileInputSynthesis = {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
/** Memory paths fed to PRE `synthesized-context` when `enabled` (validated against graph-engine allowlist). */
|
|
14
|
+
sources?: string[];
|
|
15
|
+
/** Xynthesized bucket for ai-tasks PRE `synthesized-context` `xynthesizedOutput.destination`. */
|
|
16
|
+
destination?: 'job' | 'task' | 'execution';
|
|
17
|
+
/** Required when `enabled` — key written under the destination scope. */
|
|
18
|
+
outputKey?: string;
|
|
19
|
+
/** Merge vs replace for xynthesized output (default **merge**). */
|
|
20
|
+
mode?: 'merge' | 'replace';
|
|
21
|
+
/** Catalox catalog id for synthesis mode rows (graph-composer contract). */
|
|
22
|
+
catalogId?: string;
|
|
23
|
+
strategyKey?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* PRE/POST strategies, optional web scoping, optional input synthesis for AI task nodes.
|
|
27
|
+
* `@x12i/graph-composer` requires `preStrategyKey` / `postStrategyKey` for validated AI nodes.
|
|
28
|
+
* Exellix-graph-engine merges `webScoping` into the resolved narrix payload for execution when active.
|
|
29
|
+
*
|
|
30
|
+
* **Runtime (graph-engine ≥5):** When `preStrategyKey` / `postStrategyKey` are non-empty strings, the executor issues
|
|
31
|
+
* additional `@exellix/ai-tasks` **`runTask`** calls (utility `skillKey`) **before** and **after** the MAIN node task — graph-engine does **not** call the Xynthesis package directly. Results are merged under **`execution.xynthesis.pre`** / **`execution.xynthesis.post`** (historical execution-memory slot names). This is separate from `executionPipeline` PRE `synthesized-context`, which still runs inside a single ai-tasks `runTask`.
|
|
32
|
+
*
|
|
33
|
+
* **`inputSynthesis` runtime:** When `enabled`, graph-engine translates this block into an ai-tasks PRE
|
|
34
|
+
* `synthesized-context` step inside the **same** `runTask` call (distinct from engine-level PRE/POST strategy utilities).
|
|
35
|
+
*/
|
|
36
|
+
export type AiTaskProfileMetadata = {
|
|
37
|
+
preStrategyKey?: string;
|
|
38
|
+
postStrategyKey?: string;
|
|
39
|
+
webScoping?: AiTaskProfileWebScoping;
|
|
40
|
+
inputSynthesis?: AiTaskProfileInputSynthesis;
|
|
41
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional task metadata aligned with `@x12i/graph-composer` authoring / `reportTaskNodeProtocolGaps`.
|
|
3
|
+
* Exellix-graph-engine merges {@link AiTaskProfileMetadata.webScoping} into `taskConfiguration.narrix` for execution
|
|
4
|
+
* when `enabled: true` and `questions` is non-empty (see `applyAiTaskProfileWebScopingToNarrix`).
|
|
5
|
+
*/
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable `@exellix/ai-tasks` request subtree types (package root re-exports as of **7.1.1+**; aligns with **`^7.2.2`**).
|
|
3
|
+
* Graph-engine re-exports these for callers that should not depend on `NonNullable<RunTaskRequest[...]>` patterns.
|
|
4
|
+
*/
|
|
5
|
+
export type { ExecutionStrategyInvocation, SmartInputConfig, TaskStrategyItemData, XynthesizedMemory, XynthesizedDestinationScope, XynthesizedOutputConfig, ExecutionStrategyPhase, ExecutionStrategyWrapperKey, } from '@exellix/ai-tasks';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|