@exellix/graph-engine 8.6.0 → 9.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/CHANGELOG.md +21 -0
- package/README.md +2 -2
- package/dist/src/compile/compileExellixExecutablePlan.d.ts +4 -1
- package/dist/src/compile/compileExellixExecutablePlan.js +18 -0
- package/dist/src/contract/graphRunContract.d.ts +37 -0
- package/dist/src/contract/graphRunContract.js +127 -0
- package/dist/src/contract/persistencyDefaults.d.ts +2 -0
- package/dist/src/contract/persistencyDefaults.js +2 -0
- package/dist/src/index.d.ts +11 -2
- package/dist/src/index.js +8 -1
- package/dist/src/inspection/graphInspection.js +4 -0
- package/dist/src/inspection/types.d.ts +5 -0
- package/dist/src/integrations/activixExellixShared.js +2 -2
- package/dist/src/plan/embeddedGraphToExellixGraph.js +25 -8
- package/dist/src/runtime/ExellixGraphRuntime.d.ts +2 -2
- package/dist/src/runtime/ExellixGraphRuntime.js +52 -19
- package/dist/src/runtime/coerceStringArray.d.ts +5 -0
- package/dist/src/runtime/coerceStringArray.js +34 -0
- package/dist/src/runtime/executionMatrixHost.d.ts +2 -2
- package/dist/src/runtime/executionMatrixHost.js +2 -2
- package/dist/src/runtime/graphEngineLogMeta.js +2 -2
- package/dist/src/runtime/graphEngineLogxer.js +2 -2
- package/dist/src/runtime/graphResponseMapping.d.ts +11 -0
- package/dist/src/runtime/graphResponseMapping.js +61 -8
- package/dist/src/runtime/runtimeObjects.d.ts +5 -5
- package/dist/src/runtime/runtimeObjects.js +5 -5
- package/dist/src/runtime/validateCanonicalGraphDocument.js +26 -1
- package/dist/src/types/aiTaskProfile.d.ts +2 -2
- package/dist/src/types/aiTaskProfile.js +1 -1
- package/dist/src/types/refs.d.ts +13 -8
- package/dist/src/validation/authoringGraphResponse.d.ts +21 -0
- package/dist/src/validation/authoringGraphResponse.js +88 -0
- package/dist/src/validation/validateGraphResponseWiring.d.ts +26 -0
- package/dist/src/validation/validateGraphResponseWiring.js +233 -0
- package/dist/testkit/authoringGraphFixtures.js +4 -1
- package/dist/testkit/buildExecuteGraphInput.js +3 -1
- package/dist/testkit/flatGraphToAuthoring.js +5 -5
- package/docs/handoff/graphs-studio-graphenix-2.7.3.md +13 -13
- package/package.json +17 -16
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output-only coercion for graph.response selectors with `coerce: 'stringArray'`.
|
|
3
|
+
* Same semantics as graphs-playground normalizeQaAnswersArrayFinalOutput field rules.
|
|
4
|
+
*/
|
|
5
|
+
export function coerceStringArray(value) {
|
|
6
|
+
if (value === null || value === undefined)
|
|
7
|
+
return value;
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
return value
|
|
10
|
+
.filter((item) => item != null)
|
|
11
|
+
.map((item) => String(item).trim())
|
|
12
|
+
.filter((item) => item.length > 0);
|
|
13
|
+
}
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return value;
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
if (trimmed.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
if (trimmed.startsWith('[')) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(trimmed);
|
|
22
|
+
if (Array.isArray(parsed)) {
|
|
23
|
+
return parsed
|
|
24
|
+
.filter((item) => item != null)
|
|
25
|
+
.map((item) => String(item).trim())
|
|
26
|
+
.filter((item) => item.length > 0);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// fall through to single-element wrap
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [trimmed];
|
|
34
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host helpers for execution-matrix style orchestrators (e.g. `@
|
|
2
|
+
* Host helpers for execution-matrix style orchestrators (e.g. `@x12i/exellix-runtime`).
|
|
3
3
|
*
|
|
4
4
|
* Graph-engine does not call matrix claim APIs; the host injects `executeGraph` and supplies
|
|
5
5
|
* a `{ plan, runtime }` request with per-graph `GraphEntryContract` + seeded
|
|
@@ -83,7 +83,7 @@ export declare function buildMatrixJobForGraphRun(input: {
|
|
|
83
83
|
export declare function isExecuteGraphResultFailed(result: ExecuteGraphResult): boolean;
|
|
84
84
|
/**
|
|
85
85
|
* Creates a long-lived graph runtime and exposes `executeGraph` for injection into matrix claim
|
|
86
|
-
* processors (`ProcessMatrixClaimDeps.executeGraph` in `@
|
|
86
|
+
* processors (`ProcessMatrixClaimDeps.executeGraph` in `@x12i/exellix-runtime`, etc.).
|
|
87
87
|
*/
|
|
88
88
|
export declare function createMatrixHostGraphExecutor(options: ExellixGraphRuntimeOptions): {
|
|
89
89
|
executeGraph: (input: ExecuteGraphInput) => Promise<ExecuteGraphResult>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host helpers for execution-matrix style orchestrators (e.g. `@
|
|
2
|
+
* Host helpers for execution-matrix style orchestrators (e.g. `@x12i/exellix-runtime`).
|
|
3
3
|
*
|
|
4
4
|
* Graph-engine does not call matrix claim APIs; the host injects `executeGraph` and supplies
|
|
5
5
|
* a `{ plan, runtime }` request with per-graph `GraphEntryContract` + seeded
|
|
@@ -92,7 +92,7 @@ export function isExecuteGraphResultFailed(result) {
|
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* Creates a long-lived graph runtime and exposes `executeGraph` for injection into matrix claim
|
|
95
|
-
* processors (`ProcessMatrixClaimDeps.executeGraph` in `@
|
|
95
|
+
* processors (`ProcessMatrixClaimDeps.executeGraph` in `@x12i/exellix-runtime`, etc.).
|
|
96
96
|
*/
|
|
97
97
|
export function createMatrixHostGraphExecutor(options) {
|
|
98
98
|
const { executeGraph } = createExellixGraphRuntime(options);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { X12I_GRAPH_RUNTIME_PACKAGE_NAME } from './runtimeObjects.js';
|
|
2
2
|
/** Canonical filter key for graph-engine's own log lines. */
|
|
3
3
|
export const GRAPH_ENGINE_RUNTIME_SERVICE = 'graph-engine';
|
|
4
4
|
/** Wrapper attribution when graph-engine proxies a downstream Logxer record. */
|
|
5
5
|
export const GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY = {
|
|
6
6
|
service: GRAPH_ENGINE_RUNTIME_SERVICE,
|
|
7
|
-
libraryPackage:
|
|
7
|
+
libraryPackage: X12I_GRAPH_RUNTIME_PACKAGE_NAME,
|
|
8
8
|
};
|
|
9
9
|
function isRecord(value) {
|
|
10
10
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const logxer = await import('@x12i/logxer');
|
|
2
2
|
import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
|
|
3
|
-
import {
|
|
3
|
+
import { X12I_GRAPH_RUNTIME_PACKAGE_NAME } from './runtimeObjects.js';
|
|
4
4
|
import { normalizeGraphEngineLogMeta, } from './graphEngineLogMeta.js';
|
|
5
5
|
export { GRAPH_ENGINE_RUNTIME_SERVICE, GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY, extractOriginRuntimeIdentity, normalizeGraphEngineLogMeta, } from './graphEngineLogMeta.js';
|
|
6
6
|
/** Env prefix for package-level log level: `GRAPH_ENGINE_LOGS_LEVEL` (canonical). */
|
|
@@ -116,7 +116,7 @@ function wrapGraphEngineLogxer(inner) {
|
|
|
116
116
|
/** Factory for a graph-engine Logxer instance (logxer 4.5+ stack pass-through). */
|
|
117
117
|
export function createGraphEngineLogxer(options) {
|
|
118
118
|
const inner = logxer.createLogxer({
|
|
119
|
-
packageName:
|
|
119
|
+
packageName: X12I_GRAPH_RUNTIME_PACKAGE_NAME,
|
|
120
120
|
envPrefix: GRAPH_ENGINE_LOGXER_ENV_PREFIX,
|
|
121
121
|
debugNamespace: 'graph-engine',
|
|
122
122
|
}, buildGraphEngineLogxerConfig(options?.logging));
|
|
@@ -18,6 +18,17 @@ export declare function applyGraphResponseDefinition(args: {
|
|
|
18
18
|
response: GraphResponseDefinition;
|
|
19
19
|
context: GraphResponseMappingContext;
|
|
20
20
|
}): unknown;
|
|
21
|
+
/** Resolves root `graph.response` against accumulated execution memory (FR-GE-002). */
|
|
22
|
+
export declare const resolveGraphResponse: typeof applyGraphResponseDefinition;
|
|
21
23
|
/** @deprecated Use applyGraphResponseDefinition with root-level graph.response. */
|
|
22
24
|
export declare const applyGraphResponseMapping: typeof applyGraphResponseDefinition;
|
|
25
|
+
export declare const GRAPH_RESPONSE_INCOMPLETE: "GRAPH_RESPONSE_INCOMPLETE";
|
|
26
|
+
/**
|
|
27
|
+
* When graph.response.shape is non-empty but resolution yields an empty object, surface FR-GE-006.
|
|
28
|
+
*/
|
|
29
|
+
export declare function assessGraphResponseCompleteness(response: GraphResponseDefinition, finalOutput: unknown): {
|
|
30
|
+
incomplete: boolean;
|
|
31
|
+
code?: typeof GRAPH_RESPONSE_INCOMPLETE;
|
|
32
|
+
message?: string;
|
|
33
|
+
};
|
|
23
34
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { coerceStringArray } from './coerceStringArray.js';
|
|
1
2
|
import { selectByPath } from './pathExpr.js';
|
|
2
3
|
import { readTaskNodeModelInputSurface, } from './readTaskNodeInputsConfig.js';
|
|
3
4
|
const GRAPH_RESPONSE_MAPPING_ERROR_CODE = 'GRAPH_RESPONSE_MAPPING_INVALID';
|
|
@@ -69,20 +70,31 @@ function materializeMissing(missing) {
|
|
|
69
70
|
function presentOrMissing(value, missing) {
|
|
70
71
|
return value === undefined || value === null ? materializeMissing(missing) : value;
|
|
71
72
|
}
|
|
73
|
+
function applySelectorCoercion(value, selector, path) {
|
|
74
|
+
const coerce = selector.coerce;
|
|
75
|
+
if (coerce === undefined)
|
|
76
|
+
return value;
|
|
77
|
+
if (coerce !== 'stringArray') {
|
|
78
|
+
throw createGraphResponseMappingError(`Unsupported graph.response selector coercion "${String(coerce)}".`, path, { coerce });
|
|
79
|
+
}
|
|
80
|
+
return coerceStringArray(value);
|
|
81
|
+
}
|
|
72
82
|
function resolveSelector(selector, context, path, missing) {
|
|
73
83
|
if (selector.type === 'literal') {
|
|
74
84
|
if (!Object.prototype.hasOwnProperty.call(selector, 'value')) {
|
|
75
85
|
throw createGraphResponseMappingError('literal selector requires a value field.', `${path}.value`);
|
|
76
86
|
}
|
|
77
|
-
return selector.value;
|
|
87
|
+
return applySelectorCoercion(selector.value, selector, path);
|
|
78
88
|
}
|
|
79
89
|
if (selector.type === 'outputsMemoryPath') {
|
|
80
90
|
const sourcePath = assertNonEmptyString(selector.path, `${path}.path`, `${selector.type}.path`);
|
|
81
|
-
|
|
91
|
+
const resolved = presentOrMissing(selectByPath(context.outputsMemory, sourcePath), missing);
|
|
92
|
+
return resolved === OMIT ? resolved : applySelectorCoercion(resolved, selector, path);
|
|
82
93
|
}
|
|
83
94
|
if (selector.type === 'executionMemoryPath' || selector.type === 'executionPath') {
|
|
84
95
|
const sourcePath = assertNonEmptyString(selector.path, `${path}.path`, `${selector.type}.path`);
|
|
85
|
-
|
|
96
|
+
const resolved = presentOrMissing(selectByPath(context.executionMemory, sourcePath), missing);
|
|
97
|
+
return resolved === OMIT ? resolved : applySelectorCoercion(resolved, selector, path);
|
|
86
98
|
}
|
|
87
99
|
if (selector.type === 'nodeMetadata') {
|
|
88
100
|
const nodeId = assertNonEmptyString(selector.nodeId, `${path}.nodeId`, `${selector.type}.nodeId`);
|
|
@@ -91,7 +103,8 @@ function resolveSelector(selector, context, path, missing) {
|
|
|
91
103
|
if (!node) {
|
|
92
104
|
throw createGraphResponseMappingError(`${selector.type} selector references missing nodeId "${nodeId}".`, `${path}.nodeId`, { nodeId });
|
|
93
105
|
}
|
|
94
|
-
|
|
106
|
+
const resolved = presentOrMissing(selectByPath(node.metadata, sourcePath), missing);
|
|
107
|
+
return resolved === OMIT ? resolved : applySelectorCoercion(resolved, selector, path);
|
|
95
108
|
}
|
|
96
109
|
if (selector.type === 'nodeInputsConfig') {
|
|
97
110
|
const inputSelector = selector;
|
|
@@ -102,7 +115,8 @@ function resolveSelector(selector, context, path, missing) {
|
|
|
102
115
|
throw createGraphResponseMappingError(`${selector.type} selector references missing nodeId "${nodeId}".`, `${path}.nodeId`, { nodeId });
|
|
103
116
|
}
|
|
104
117
|
const root = readTaskNodeModelInputSurface(node);
|
|
105
|
-
|
|
118
|
+
const resolved = presentOrMissing(selectByPath(root, sourcePath), missing);
|
|
119
|
+
return resolved === OMIT ? resolved : applySelectorCoercion(resolved, selector, path);
|
|
106
120
|
}
|
|
107
121
|
if (selector.type === 'firstPresent') {
|
|
108
122
|
if (!Array.isArray(selector.sources) || selector.sources.length === 0) {
|
|
@@ -114,10 +128,12 @@ function resolveSelector(selector, context, path, missing) {
|
|
|
114
128
|
throw createGraphResponseMappingError('firstPresent.sources entries must be graph response mapping selectors.', `${path}.sources.${i}`);
|
|
115
129
|
}
|
|
116
130
|
const value = resolveSelector(source, context, `${path}.sources.${i}`, 'omit');
|
|
117
|
-
if (value !== OMIT && value !== null)
|
|
118
|
-
return value;
|
|
131
|
+
if (value !== OMIT && value !== null) {
|
|
132
|
+
return applySelectorCoercion(value, selector, path);
|
|
133
|
+
}
|
|
119
134
|
}
|
|
120
|
-
|
|
135
|
+
const missingValue = materializeMissing(missing);
|
|
136
|
+
return missingValue === OMIT ? missingValue : applySelectorCoercion(missingValue, selector, path);
|
|
121
137
|
}
|
|
122
138
|
throw createGraphResponseMappingError(`Unsupported graph response mapping selector type "${String(selector.type)}".`, `${path}.type`);
|
|
123
139
|
}
|
|
@@ -151,5 +167,42 @@ export function applyGraphResponseDefinition(args) {
|
|
|
151
167
|
const mapped = resolveShape(args.response.shape, args.context, 'graph.response.shape', missing);
|
|
152
168
|
return mapped === OMIT ? undefined : mapped;
|
|
153
169
|
}
|
|
170
|
+
/** Resolves root `graph.response` against accumulated execution memory (FR-GE-002). */
|
|
171
|
+
export const resolveGraphResponse = applyGraphResponseDefinition;
|
|
154
172
|
/** @deprecated Use applyGraphResponseDefinition with root-level graph.response. */
|
|
155
173
|
export const applyGraphResponseMapping = applyGraphResponseDefinition;
|
|
174
|
+
export const GRAPH_RESPONSE_INCOMPLETE = 'GRAPH_RESPONSE_INCOMPLETE';
|
|
175
|
+
function isNonEmptyResponseShape(shape) {
|
|
176
|
+
if (shape == null)
|
|
177
|
+
return false;
|
|
178
|
+
if (Array.isArray(shape))
|
|
179
|
+
return shape.length > 0;
|
|
180
|
+
if (isPlainObject(shape))
|
|
181
|
+
return Object.keys(shape).length > 0;
|
|
182
|
+
return isSupportedSelector(shape);
|
|
183
|
+
}
|
|
184
|
+
function isEmptyResolvedOutput(value) {
|
|
185
|
+
if (value === undefined || value === null)
|
|
186
|
+
return true;
|
|
187
|
+
if (Array.isArray(value))
|
|
188
|
+
return value.length === 0;
|
|
189
|
+
if (isPlainObject(value))
|
|
190
|
+
return Object.keys(value).length === 0;
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* When graph.response.shape is non-empty but resolution yields an empty object, surface FR-GE-006.
|
|
195
|
+
*/
|
|
196
|
+
export function assessGraphResponseCompleteness(response, finalOutput) {
|
|
197
|
+
if (!isNonEmptyResponseShape(response.shape)) {
|
|
198
|
+
return { incomplete: false };
|
|
199
|
+
}
|
|
200
|
+
if (!isEmptyResolvedOutput(finalOutput)) {
|
|
201
|
+
return { incomplete: false };
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
incomplete: true,
|
|
205
|
+
code: GRAPH_RESPONSE_INCOMPLETE,
|
|
206
|
+
message: 'graph.response.shape is non-empty but finalOutput resolved empty — check executionMapping paths and selectors.',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { GetJobLogsInput, GetJobLogsResult, QueryableLogLine } from '@x12i/logxer';
|
|
6
6
|
/** Published npm name of this package (root layer in composed observability). */
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const X12I_GRAPH_RUNTIME_PACKAGE_NAME: "@exellix/graph-engine";
|
|
8
8
|
/** Task layer package name in {@link RuntimeObjects.packagesRuntimeObjects}. */
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const X12I_AI_TASKS_PACKAGE_NAME: "@exellix/ai-tasks";
|
|
10
10
|
export type ActivixQueryableClient = {
|
|
11
11
|
getJobActivities(input: {
|
|
12
12
|
jobId: string;
|
|
@@ -43,15 +43,15 @@ export type BuildExellixGraphRuntimeObjectsInput = {
|
|
|
43
43
|
graphLogxerClient?: LogxerQueryableClient;
|
|
44
44
|
/**
|
|
45
45
|
* Partial subtree from `@exellix/ai-tasks` when that package exports `runtimeObjects`.
|
|
46
|
-
* Nested `packagesRuntimeObjects` are appended after the
|
|
46
|
+
* Nested `packagesRuntimeObjects` are appended after the x12i-tasks row without overwriting entries.
|
|
47
47
|
*/
|
|
48
48
|
aiTasksRuntimeObjects?: Pick<RuntimeObjects, 'activixClient' | 'logxerClient'> & {
|
|
49
49
|
packagesRuntimeObjects?: PackageRuntimeObjects[];
|
|
50
50
|
};
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
* Builds the composed {@link RuntimeObjects} for
|
|
54
|
-
* then an `@exellix/ai-tasks` package row plus any nested package rows from
|
|
53
|
+
* Builds the composed {@link RuntimeObjects} for x12i-graph: graph-level clients at the root,
|
|
54
|
+
* then an `@exellix/ai-tasks` package row plus any nested package rows from x12i-tasks (flattened as siblings).
|
|
55
55
|
*/
|
|
56
56
|
export declare function buildExellixGraphRuntimeObjects(input: BuildExellixGraphRuntimeObjectsInput): RuntimeObjects;
|
|
57
57
|
/**
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/** Published npm name of this package (root layer in composed observability). */
|
|
2
|
-
export const
|
|
2
|
+
export const X12I_GRAPH_RUNTIME_PACKAGE_NAME = '@exellix/graph-engine';
|
|
3
3
|
/** Task layer package name in {@link RuntimeObjects.packagesRuntimeObjects}. */
|
|
4
|
-
export const
|
|
4
|
+
export const X12I_AI_TASKS_PACKAGE_NAME = '@exellix/ai-tasks';
|
|
5
5
|
/**
|
|
6
|
-
* Builds the composed {@link RuntimeObjects} for
|
|
7
|
-
* then an `@exellix/ai-tasks` package row plus any nested package rows from
|
|
6
|
+
* Builds the composed {@link RuntimeObjects} for x12i-graph: graph-level clients at the root,
|
|
7
|
+
* then an `@exellix/ai-tasks` package row plus any nested package rows from x12i-tasks (flattened as siblings).
|
|
8
8
|
*/
|
|
9
9
|
export function buildExellixGraphRuntimeObjects(input) {
|
|
10
10
|
const ai = input.aiTasksRuntimeObjects;
|
|
11
11
|
const tail = [...(ai?.packagesRuntimeObjects ?? [])];
|
|
12
12
|
const packagesRuntimeObjects = [
|
|
13
13
|
{
|
|
14
|
-
name:
|
|
14
|
+
name: X12I_AI_TASKS_PACKAGE_NAME,
|
|
15
15
|
activixClient: ai?.activixClient,
|
|
16
16
|
logxerClient: ai?.logxerClient,
|
|
17
17
|
},
|
|
@@ -40,6 +40,8 @@ const FORBIDDEN_GRAPH_RESPONSE_KEYS = [
|
|
|
40
40
|
'notableExecutionPaths',
|
|
41
41
|
'mappingPreset',
|
|
42
42
|
];
|
|
43
|
+
const LEGACY_RESPONSE_SHAPE_KEYS = new Set(['subnetAnalysis', 'taskSections']);
|
|
44
|
+
const LEGACY_EXECUTION_MAPPING_PATH_RE = /^inference\.conceptSketch(\.|$)/;
|
|
43
45
|
/**
|
|
44
46
|
* Returns top-level keys on `graph` that are not part of the canonical executable document contract.
|
|
45
47
|
*/
|
|
@@ -431,6 +433,11 @@ function assertGraphResponseShapeNoLegacy(shape, path, context) {
|
|
|
431
433
|
}
|
|
432
434
|
if (!isPlainObject(shape))
|
|
433
435
|
return;
|
|
436
|
+
for (const key of Object.keys(shape)) {
|
|
437
|
+
if (LEGACY_RESPONSE_SHAPE_KEYS.has(key)) {
|
|
438
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.${key} is a removed legacy response shape key.`, { ...context, responseShapeKey: key });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
434
441
|
if (shape.type === 'nodeInputs') {
|
|
435
442
|
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${path}.type "nodeInputs" was removed; use nodeInputsConfig.`, context);
|
|
436
443
|
}
|
|
@@ -478,7 +485,21 @@ export function assertCanonicalTaskNode(node, context, options) {
|
|
|
478
485
|
if (!isTaskShape(node))
|
|
479
486
|
return;
|
|
480
487
|
if ('outputMapping' in node) {
|
|
481
|
-
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) });
|
|
488
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": TASK_OUTPUT_MAPPING_DEPRECATED — outputMapping belongs on the finalizer. Use executionMapping to write task results into executionMemory.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
489
|
+
}
|
|
490
|
+
const executionMapping = node.executionMapping;
|
|
491
|
+
if (executionMapping?.path &&
|
|
492
|
+
typeof executionMapping.path === 'string' &&
|
|
493
|
+
LEGACY_EXECUTION_MAPPING_PATH_RE.test(executionMapping.path)) {
|
|
494
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": EXECUTION_MAPPING_LEGACY_PATH — use answers.qN instead of inference.conceptSketch.stepN.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
495
|
+
}
|
|
496
|
+
const map = executionMapping?.map;
|
|
497
|
+
if (isPlainObject(map)) {
|
|
498
|
+
for (const targetKey of Object.keys(map)) {
|
|
499
|
+
if (targetKey.includes('.')) {
|
|
500
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_TASK_NODE, `Task node "${String(node.id)}": TASK_MAP_TARGET_INVALID — executionMapping.map target "${targetKey}" must be flat.`, { jobId: context?.jobId, graphId: context?.graphId, nodeId: String(node.id) });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
482
503
|
}
|
|
483
504
|
assertOptionalTaskNodeConditions(node.conditions, String(node.id), {
|
|
484
505
|
jobId: context?.jobId,
|
|
@@ -670,6 +691,10 @@ export function assertCanonicalGraphDocument(graph, context, options) {
|
|
|
670
691
|
jobId: context?.jobId,
|
|
671
692
|
graphId: resolvedGraphId,
|
|
672
693
|
});
|
|
694
|
+
const preset = metadata.responsePreset;
|
|
695
|
+
if (isPlainObject(preset) && preset.id === 'subnetAnalysis') {
|
|
696
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, 'metadata.responsePreset.id "subnetAnalysis" was removed — migrate to graph.response.shape.', { jobId: context?.jobId, graphId: resolvedGraphId });
|
|
697
|
+
}
|
|
673
698
|
const graphEntry = metadata.graphEntry;
|
|
674
699
|
if (graphEntry != null && typeof graphEntry === 'object' && !Array.isArray(graphEntry)) {
|
|
675
700
|
const ge = graphEntry;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Optional task metadata aligned with `@
|
|
2
|
+
* Optional task metadata aligned with `@exellix/graph-composer` authoring / `reportTaskNodeProtocolGaps`.
|
|
3
3
|
* Web scope is authored via {@link AiTaskProfileMetadata.webQueryTemplate} (Graphenix 2.7.3+ PRE `webScope` unit).
|
|
4
4
|
*/
|
|
5
5
|
export type AiTaskProfileInputSynthesis = {
|
|
@@ -18,7 +18,7 @@ export type AiTaskProfileInputSynthesis = {
|
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* PRE/POST strategies, optional web query template, optional input synthesis for AI task nodes.
|
|
21
|
-
* `@
|
|
21
|
+
* `@exellix/graph-composer` requires `preStrategyKey` / `postStrategyKey` for validated AI nodes.
|
|
22
22
|
*
|
|
23
23
|
* **Web scope (Graphenix 2.7.3+):** Non-empty `webQueryTemplate` (or `webQueryTemplates[]`) compiles to a PRE
|
|
24
24
|
* `webScope` unit; graph-engine does not merge web intent into `taskConfiguration.narrix`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Optional task metadata aligned with `@
|
|
2
|
+
* Optional task metadata aligned with `@exellix/graph-composer` authoring / `reportTaskNodeProtocolGaps`.
|
|
3
3
|
* Web scope is authored via {@link AiTaskProfileMetadata.webQueryTemplate} (Graphenix 2.7.3+ PRE `webScope` unit).
|
|
4
4
|
*/
|
|
5
5
|
export function hasWebScopeAuthoring(profile) {
|
package/dist/src/types/refs.d.ts
CHANGED
|
@@ -133,30 +133,35 @@ export type FinalizerInputBinding = {
|
|
|
133
133
|
value: unknown;
|
|
134
134
|
};
|
|
135
135
|
export type GraphResponseMissingBehavior = 'omit' | 'null';
|
|
136
|
-
|
|
136
|
+
/** Post-resolution output coercion (9.x). Only `stringArray` is supported in 9.0. */
|
|
137
|
+
export type GraphResponseCoercion = 'stringArray';
|
|
138
|
+
type GraphResponseSelectorBase = {
|
|
139
|
+
coerce?: GraphResponseCoercion;
|
|
140
|
+
};
|
|
141
|
+
export type GraphResponseSelector = (GraphResponseSelectorBase & {
|
|
137
142
|
type: 'outputsMemoryPath';
|
|
138
143
|
path: string;
|
|
139
|
-
} | {
|
|
144
|
+
}) | (GraphResponseSelectorBase & {
|
|
140
145
|
type: 'executionMemoryPath';
|
|
141
146
|
path: string;
|
|
142
|
-
} | {
|
|
147
|
+
}) | (GraphResponseSelectorBase & {
|
|
143
148
|
type: 'executionPath';
|
|
144
149
|
path: string;
|
|
145
|
-
} | {
|
|
150
|
+
}) | (GraphResponseSelectorBase & {
|
|
146
151
|
type: 'nodeMetadata';
|
|
147
152
|
nodeId: string;
|
|
148
153
|
path: string;
|
|
149
|
-
} | {
|
|
154
|
+
}) | (GraphResponseSelectorBase & {
|
|
150
155
|
type: 'nodeInputsConfig';
|
|
151
156
|
nodeId: string;
|
|
152
157
|
path: string;
|
|
153
|
-
} | {
|
|
158
|
+
}) | (GraphResponseSelectorBase & {
|
|
154
159
|
type: 'literal';
|
|
155
160
|
value: unknown;
|
|
156
|
-
} | {
|
|
161
|
+
}) | (GraphResponseSelectorBase & {
|
|
157
162
|
type: 'firstPresent';
|
|
158
163
|
sources: GraphResponseSelector[];
|
|
159
|
-
};
|
|
164
|
+
});
|
|
160
165
|
export type GraphResponseShape = unknown;
|
|
161
166
|
export type GraphResponseDefinition = {
|
|
162
167
|
/** Default: "omit". When "null", missing selector values are materialized as null. */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AuthoringGraphDocument } from '@x12i/graphenix-executable-contracts';
|
|
2
|
+
import type { GraphResponseDefinition } from '../types/refs.js';
|
|
3
|
+
export declare const GRAPH_RESPONSE_LEGACY_SOURCE: "GRAPH_RESPONSE_LEGACY_SOURCE";
|
|
4
|
+
export declare const GRAPH_RESPONSE_DUAL_SOURCE: "GRAPH_RESPONSE_DUAL_SOURCE";
|
|
5
|
+
export type AuthoringResponseSourceIssue = {
|
|
6
|
+
code: typeof GRAPH_RESPONSE_LEGACY_SOURCE | typeof GRAPH_RESPONSE_DUAL_SOURCE;
|
|
7
|
+
message: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Import-only helper: copy metadata.graphResponse → graph.response when graph.response is absent.
|
|
12
|
+
* Does not run on the execute hot path.
|
|
13
|
+
*/
|
|
14
|
+
export declare function migrateLegacyGraphResponseToAuthoring(doc: AuthoringGraphDocument): AuthoringGraphDocument;
|
|
15
|
+
/** Detect legacy-only or dual authoring response sources (FR-GE-001 / CR-GE-001). */
|
|
16
|
+
export declare function detectAuthoringResponseSourceIssues(doc: AuthoringGraphDocument): AuthoringResponseSourceIssue[];
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the single canonical authoring response definition.
|
|
19
|
+
* @throws Error with code GRAPH_RESPONSE_LEGACY_SOURCE when only legacy metadata carries the shape.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveAuthoringGraphResponse(doc: AuthoringGraphDocument): GraphResponseDefinition;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export const GRAPH_RESPONSE_LEGACY_SOURCE = 'GRAPH_RESPONSE_LEGACY_SOURCE';
|
|
2
|
+
export const GRAPH_RESPONSE_DUAL_SOURCE = 'GRAPH_RESPONSE_DUAL_SOURCE';
|
|
3
|
+
/**
|
|
4
|
+
* Import-only helper: copy metadata.graphResponse → graph.response when graph.response is absent.
|
|
5
|
+
* Does not run on the execute hot path.
|
|
6
|
+
*/
|
|
7
|
+
export function migrateLegacyGraphResponseToAuthoring(doc) {
|
|
8
|
+
const next = structuredClone(doc);
|
|
9
|
+
const graph = next.graph;
|
|
10
|
+
const hasGraphResponse = isPlainRecord(graph.response) && 'shape' in graph.response;
|
|
11
|
+
const metaResponse = next.graph.metadata?.graphResponse;
|
|
12
|
+
if (!hasGraphResponse && isPlainRecord(metaResponse) && 'shape' in metaResponse) {
|
|
13
|
+
graph.response = {
|
|
14
|
+
...(metaResponse.missing !== undefined ? { missing: metaResponse.missing } : {}),
|
|
15
|
+
shape: structuredClone(metaResponse.shape),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
20
|
+
function isPlainRecord(v) {
|
|
21
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
22
|
+
}
|
|
23
|
+
function hasExecutableResponseShape(response) {
|
|
24
|
+
if (!isPlainRecord(response))
|
|
25
|
+
return false;
|
|
26
|
+
const shape = response.shape;
|
|
27
|
+
if (shape === undefined)
|
|
28
|
+
return false;
|
|
29
|
+
if (isPlainRecord(shape))
|
|
30
|
+
return Object.keys(shape).length > 0;
|
|
31
|
+
if (Array.isArray(shape))
|
|
32
|
+
return shape.length > 0;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
function readGraphLevelResponse(doc) {
|
|
36
|
+
const graph = doc.graph;
|
|
37
|
+
const response = graph.response;
|
|
38
|
+
if (!isPlainRecord(response) || !('shape' in response))
|
|
39
|
+
return undefined;
|
|
40
|
+
return response;
|
|
41
|
+
}
|
|
42
|
+
function readMetadataGraphResponse(doc) {
|
|
43
|
+
const metaResponse = doc.graph.metadata?.graphResponse;
|
|
44
|
+
if (!isPlainRecord(metaResponse) || !('shape' in metaResponse))
|
|
45
|
+
return undefined;
|
|
46
|
+
return metaResponse;
|
|
47
|
+
}
|
|
48
|
+
/** Detect legacy-only or dual authoring response sources (FR-GE-001 / CR-GE-001). */
|
|
49
|
+
export function detectAuthoringResponseSourceIssues(doc) {
|
|
50
|
+
const issues = [];
|
|
51
|
+
const graphResponse = readGraphLevelResponse(doc);
|
|
52
|
+
const metaResponse = readMetadataGraphResponse(doc);
|
|
53
|
+
const graphHasShape = graphResponse != null && hasExecutableResponseShape(graphResponse);
|
|
54
|
+
const metaHasShape = metaResponse != null && hasExecutableResponseShape(metaResponse);
|
|
55
|
+
if (!graphHasShape && metaHasShape) {
|
|
56
|
+
issues.push({
|
|
57
|
+
code: GRAPH_RESPONSE_LEGACY_SOURCE,
|
|
58
|
+
message: 'Executable response mapping exists only under metadata.graphResponse. Move shape to graph.response — studio metadata is UI-only and is not read at compile or execute.',
|
|
59
|
+
path: 'graph.metadata.graphResponse',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (graphHasShape && metaHasShape) {
|
|
63
|
+
issues.push({
|
|
64
|
+
code: GRAPH_RESPONSE_DUAL_SOURCE,
|
|
65
|
+
message: 'Response mapping is declared on both graph.response and metadata.graphResponse. graph.response is authoritative; remove executable shape from metadata.graphResponse.',
|
|
66
|
+
path: 'graph.metadata.graphResponse',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return issues;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolves the single canonical authoring response definition.
|
|
73
|
+
* @throws Error with code GRAPH_RESPONSE_LEGACY_SOURCE when only legacy metadata carries the shape.
|
|
74
|
+
*/
|
|
75
|
+
export function resolveAuthoringGraphResponse(doc) {
|
|
76
|
+
const issues = detectAuthoringResponseSourceIssues(doc);
|
|
77
|
+
const blocking = issues.find((i) => i.code === GRAPH_RESPONSE_LEGACY_SOURCE);
|
|
78
|
+
if (blocking) {
|
|
79
|
+
const err = new Error(blocking.message);
|
|
80
|
+
err.code = GRAPH_RESPONSE_LEGACY_SOURCE;
|
|
81
|
+
err.path = blocking.path;
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
const graphResponse = readGraphLevelResponse(doc);
|
|
85
|
+
if (graphResponse)
|
|
86
|
+
return graphResponse;
|
|
87
|
+
return { shape: {} };
|
|
88
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AuthoringGraphDocument, ExecutableGraphPlanV2 } from '@x12i/graphenix-executable-contracts';
|
|
2
|
+
import type { Graph } from '../types/refs.js';
|
|
3
|
+
export declare const RESPONSE_PATH_NO_WRITER: "RESPONSE_PATH_NO_WRITER";
|
|
4
|
+
export declare const TASK_MAP_TARGET_INVALID: "TASK_MAP_TARGET_INVALID";
|
|
5
|
+
export declare const TASK_MAP_SOURCE_INVALID: "TASK_MAP_SOURCE_INVALID";
|
|
6
|
+
export declare const RESPONSE_LEGACY_SHAPE_KEY: "RESPONSE_LEGACY_SHAPE_KEY";
|
|
7
|
+
export declare const EXECUTION_MAPPING_LEGACY_PATH: "EXECUTION_MAPPING_LEGACY_PATH";
|
|
8
|
+
export declare const RESPONSE_EMPTY_SHAPE: "RESPONSE_EMPTY_SHAPE";
|
|
9
|
+
export declare const RESPONSE_LEGACY_PRESET_ID: "RESPONSE_LEGACY_PRESET_ID";
|
|
10
|
+
export declare const RESPONSE_COERCE_STRING_ARRAY_ON_SCALAR: "RESPONSE_COERCE_STRING_ARRAY_ON_SCALAR";
|
|
11
|
+
export type GraphResponseWiringIssue = {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
nodeId?: string;
|
|
15
|
+
path?: string;
|
|
16
|
+
};
|
|
17
|
+
export type GraphResponseWiringValidation = {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
errors: GraphResponseWiringIssue[];
|
|
20
|
+
warnings: GraphResponseWiringIssue[];
|
|
21
|
+
};
|
|
22
|
+
/** Validate response wiring for an authoring document, executable plan, or materialized graph. */
|
|
23
|
+
export declare function validateGraphResponseWiring(input: AuthoringGraphDocument | ExecutableGraphPlanV2 | Graph): GraphResponseWiringValidation;
|
|
24
|
+
export declare function assertGraphResponseWiringOk(validation: GraphResponseWiringValidation, context?: {
|
|
25
|
+
graphId?: string;
|
|
26
|
+
}): void;
|