@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +2 -2
  3. package/dist/src/compile/compileExellixExecutablePlan.d.ts +4 -1
  4. package/dist/src/compile/compileExellixExecutablePlan.js +18 -0
  5. package/dist/src/contract/graphRunContract.d.ts +37 -0
  6. package/dist/src/contract/graphRunContract.js +127 -0
  7. package/dist/src/contract/persistencyDefaults.d.ts +2 -0
  8. package/dist/src/contract/persistencyDefaults.js +2 -0
  9. package/dist/src/index.d.ts +11 -2
  10. package/dist/src/index.js +8 -1
  11. package/dist/src/inspection/graphInspection.js +4 -0
  12. package/dist/src/inspection/types.d.ts +5 -0
  13. package/dist/src/integrations/activixExellixShared.js +2 -2
  14. package/dist/src/plan/embeddedGraphToExellixGraph.js +25 -8
  15. package/dist/src/runtime/ExellixGraphRuntime.d.ts +2 -2
  16. package/dist/src/runtime/ExellixGraphRuntime.js +52 -19
  17. package/dist/src/runtime/coerceStringArray.d.ts +5 -0
  18. package/dist/src/runtime/coerceStringArray.js +34 -0
  19. package/dist/src/runtime/executionMatrixHost.d.ts +2 -2
  20. package/dist/src/runtime/executionMatrixHost.js +2 -2
  21. package/dist/src/runtime/graphEngineLogMeta.js +2 -2
  22. package/dist/src/runtime/graphEngineLogxer.js +2 -2
  23. package/dist/src/runtime/graphResponseMapping.d.ts +11 -0
  24. package/dist/src/runtime/graphResponseMapping.js +61 -8
  25. package/dist/src/runtime/runtimeObjects.d.ts +5 -5
  26. package/dist/src/runtime/runtimeObjects.js +5 -5
  27. package/dist/src/runtime/validateCanonicalGraphDocument.js +26 -1
  28. package/dist/src/types/aiTaskProfile.d.ts +2 -2
  29. package/dist/src/types/aiTaskProfile.js +1 -1
  30. package/dist/src/types/refs.d.ts +13 -8
  31. package/dist/src/validation/authoringGraphResponse.d.ts +21 -0
  32. package/dist/src/validation/authoringGraphResponse.js +88 -0
  33. package/dist/src/validation/validateGraphResponseWiring.d.ts +26 -0
  34. package/dist/src/validation/validateGraphResponseWiring.js +233 -0
  35. package/dist/testkit/authoringGraphFixtures.js +4 -1
  36. package/dist/testkit/buildExecuteGraphInput.js +3 -1
  37. package/dist/testkit/flatGraphToAuthoring.js +5 -5
  38. package/docs/handoff/graphs-studio-graphenix-2.7.3.md +13 -13
  39. package/package.json +17 -16
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.0.0 — Response contract & finalOutput (FR-GE / CR-GE pack)
4
+
5
+ ### Breaking
6
+
7
+ - **Single canonical response:** Executable mapping reads **`graph.response` only** (authoring: `graph.response`; flat model: root `response`). `metadata.graphResponse` is rejected as executable truth (`GRAPH_RESPONSE_LEGACY_SOURCE`).
8
+ - **`validateGraphResponseWiring`** runs on compile by default (`strictResponseValidation !== false`): path writers, flat map targets, legacy paths/shapes, empty shape + finalizer.
9
+ - **`GRAPH_RESPONSE_INCOMPLETE`** when non-empty `graph.response.shape` resolves empty on a completed run.
10
+ - **Legacy removed:** `inference.conceptSketch.*` execution paths, `subnetAnalysis` / top-level `taskSections` shape keys, `metadata.responsePreset.id === 'subnetAnalysis'`, task `outputMapping`.
11
+ - **`resolveGraphRunContract`** reads **`graph.response.persistency` only** (no `metadata.graphResponse.persistency` fallback).
12
+ - **`ExecuteGraphResult.execution`** is always present on graph runs.
13
+
14
+ ### Added
15
+
16
+ - **`coerce: 'stringArray'`** on response selectors; **`coerceStringArray`**, **`resolveGraphResponse`**, **`assessGraphResponseCompleteness`** exports.
17
+ - **`migrateLegacyGraphResponseToAuthoring`** (import-only).
18
+ - **`inspectGraph`**: `response.source === 'graph.response'`.
19
+
20
+ ### Downstream
21
+
22
+ - **graphs-playground:** remove `normalizeQaAnswersArrayFinalOutput`, `normalizeTaskSectionsFinalOutput`, `coerceResponsePresetTaggedGraph`, `buildTaskSectionsFinalOutputFromAnswers`; add `coerce: 'stringArray'` in studio preset builders at save time.
23
+
3
24
  ## 8.6.0 — Graphenix 2.7.3 (no-legacy web + narrix pipeline)
4
25
 
5
26
  ### Breaking
package/README.md CHANGED
@@ -55,7 +55,7 @@ npm install @exellix/graph-engine
55
55
 
56
56
  **Upstream tasks SDK:** This package depends on **`@exellix/ai-tasks` ^8.6.8** and **`@x12i/funcx` 4.4.4** (see **`package.json`**). Graph-engine emits **`RunTaskRequest`** shapes that match the ai-tasks **8.x** closed schema (mandatory **`executionStrategies`**, three-slot **`modelConfig`**, **`xynthesized`**, optional **`smartInput`**, no legacy root mirrors). Pin compatible versions in your app lockfile. Optional CI improvement: fail or warn when the resolved ai-tasks major/minor drifts outside an allowlist (not enforced in-repo today).
57
57
 
58
- ### Execution matrix hosts (`@exellix/exellix-runtime`) — documentation only for the engine
58
+ ### Execution matrix hosts (`@x12i/exellix-runtime`) — documentation only for the engine
59
59
 
60
60
  Matrix **claim**, **rows**, and **retry policy** live outside this package. If your host wires **matrix → graph run**, see [.docs/execution-matrix-handoff.md](.docs/execution-matrix-handoff.md) for how **you** should inject `runtime.executeGraph`, compile **`GraphModelObject` → plan**, resolve **`metadata.graphEntry` per model**, seed **`runtime.executionMemory`**, and pass **`runtime.jobId`**. Helpers such as **`buildMatrixJobForGraphRun`** align **`id`** and **`job.jobId`** on the `job` object so you can call **`runtime.executeGraph({ plan, runtime: { jobId: job.jobId, job, … } })`**. Those exports are **optional helpers for callers**; they do **not** expand graph-engine’s role beyond **executing the single run** when invoked.
61
61
 
@@ -329,7 +329,7 @@ When **`taskConfiguration.aiTaskProfile.inputSynthesis`** is enabled, graph-engi
329
329
 
330
330
  1. **Engine PRE/POST strategy utilities (ai-tasks only):** `taskConfiguration.aiTaskProfile.preStrategyKey` / `postStrategyKey` → extra **`runTask`** calls via **`@exellix/ai-tasks`** before/after MAIN; outputs stored at **`execution.xynthesis.pre`** / **`execution.xynthesis.post`** (historical slot names).
331
331
  2. **`executionPipeline` inside ai-tasks:** e.g. PRE **`synthesized-context`** + MAIN direct — still **one** outbound MAIN `runTask` handled inside `@exellix/ai-tasks`.
332
- 3. **Narrix web scope inside ai-tasks:** when **`taskConfiguration.aiTaskProfile.webScoping.enabled`** is true, graph-engine forwards a `narrix` payload with `enableWebScope` / `webScopeQuestions`. The actual web fetch + skip rules run **inside `@exellix/ai-tasks`** (`@exellix/narrix-web-scoper`); graph-engine has **no local web phase** in 5.x.
332
+ 3. **Narrix web scope inside ai-tasks:** when **`taskConfiguration.aiTaskProfile.webScoping.enabled`** is true, graph-engine forwards a `narrix` payload with `enableWebScope` / `webScopeQuestions`. The actual web fetch + skip rules run **inside `@exellix/ai-tasks`** (`@x12i/narrix-web-scoper`); graph-engine has **no local web phase** in 5.x.
333
333
 
334
334
  `synthesize` finalizers are a fourth outbound task-call shape: the finalizer is still a graph model node, but its terminal utility `runTask` uses the same run identity, diagnostics, `llmCall`, and resolved model config as the rest of the graph run.
335
335
 
@@ -1,6 +1,9 @@
1
1
  import type { AuthoringGraphDocument, CompileExecutablePlanV2Options, ExecutableGraphPlanV2 } from '@x12i/graphenix-executable-contracts';
2
2
  import type { GraphRuntimeObject } from '../runtime/ExellixGraphRuntime.js';
3
- export type CompileExellixExecutablePlanOptions = CompileExecutablePlanV2Options;
3
+ export type CompileExellixExecutablePlanOptions = CompileExecutablePlanV2Options & {
4
+ /** When true (default in 9.x), reject unsatisfiable graph.response wiring before returning a plan. */
5
+ strictResponseValidation?: boolean;
6
+ };
4
7
  /**
5
8
  * Host/test helper: canonical {@link AuthoringGraphDocument} + runtime → validated v2 executable plan.
6
9
  * No legacy migration — graphs must already be Graphenix 2.x authoring shape.
@@ -3,6 +3,12 @@ import { compileExecutablePlanV2 } from '@x12i/graphenix-plan-compiler';
3
3
  import { validateExecutablePlanV2 } from '@x12i/graphenix-plan-format';
4
4
  import { resolveGraphEntryFromAuthoringDocument } from './authoringDocumentHelpers.js';
5
5
  import { EXELLIX_STRUCTURED_DATA_FILTERS_V1 } from '../types/refs.js';
6
+ import { assertGraphResponseWiringOk, validateGraphResponseWiring, } from '../validation/validateGraphResponseWiring.js';
7
+ import { resolveAuthoringGraphResponse } from '../validation/authoringGraphResponse.js';
8
+ import { assertGraphResponseDefinition } from '../runtime/graphResponseMapping.js';
9
+ function isPlainRecord(v) {
10
+ return v != null && typeof v === 'object' && !Array.isArray(v);
11
+ }
6
12
  function patchEntryGatesFromGraphEntryDataFilters(plan, doc) {
7
13
  const graphEntry = resolveGraphEntryFromAuthoringDocument(doc);
8
14
  const dataFilters = graphEntry?.dataFilters;
@@ -30,6 +36,14 @@ function patchEntryGatesFromGraphEntryDataFilters(plan, doc) {
30
36
  * No legacy migration — graphs must already be Graphenix 2.x authoring shape.
31
37
  */
32
38
  export function compileExellixExecutablePlan(doc, runtime, options) {
39
+ const strictResponseValidation = options?.strictResponseValidation !== false;
40
+ // Ensure canonical response is present on authoring graph before plan compile (FR-GE-001).
41
+ const resolvedResponse = resolveAuthoringGraphResponse(doc);
42
+ assertGraphResponseDefinition(resolvedResponse);
43
+ const graphRecord = doc.graph;
44
+ if (!isPlainRecord(graphRecord.response)) {
45
+ graphRecord.response = resolvedResponse;
46
+ }
33
47
  const authoringValidation = validateAuthoringGraph(doc);
34
48
  if (!authoringValidation.valid) {
35
49
  const summary = authoringValidation.errors.map((e) => `${e.path}: ${e.message}`).join('; ');
@@ -41,5 +55,9 @@ export function compileExellixExecutablePlan(doc, runtime, options) {
41
55
  const summary = planValidation.errors.map((e) => `${e.path}: ${e.message}`).join('; ');
42
56
  throw new Error(`compileExellixExecutablePlan: invalid plan: ${summary}`);
43
57
  }
58
+ if (strictResponseValidation) {
59
+ const planWiring = validateGraphResponseWiring(plan);
60
+ assertGraphResponseWiringOk(planWiring, { graphId: plan.source.graphId });
61
+ }
44
62
  return plan;
45
63
  }
@@ -0,0 +1,37 @@
1
+ export type GraphRunPersistencyLink = {
2
+ /** Relation descriptor key (result entity → source entity). */
3
+ relationKey?: string;
4
+ /** Dot-path on the new record where the source recordId/entityId FK is written. */
5
+ fkPath?: string;
6
+ type?: 'oneToOne' | 'oneToMany' | 'manyToOne' | 'manyToMany';
7
+ /** Content type the relation targets on the source (default: source job contentType). */
8
+ targetContentType?: string;
9
+ };
10
+ export type GraphRunPersistency = {
11
+ /** Target object type; default `same-as-input`. */
12
+ entityId: string | 'same-as-input';
13
+ /** Result collection content type; default {@link DEFAULT_GRAPH_RUN_PERSISTENCY_CONTENT_TYPE}. */
14
+ contentType: string;
15
+ /** When true (default), always insert a new result record with a new recordId. */
16
+ newRecord?: boolean;
17
+ /** Cross-object link intent; auto-derived when {@link entityId} differs from the job source. */
18
+ link?: GraphRunPersistencyLink;
19
+ };
20
+ export type GraphRunContract = {
21
+ graphId: string;
22
+ persistency: GraphRunPersistency | null;
23
+ graphVersion?: string;
24
+ };
25
+ /** Parse a work/job/graph persistency object (partial fields allowed). */
26
+ export declare function parseGraphRunPersistency(raw: unknown): GraphRunPersistency | null;
27
+ /** Work/job overrides win over graph defaults; partial overrides merge field-by-field. */
28
+ export declare function mergeGraphRunPersistency(base: GraphRunPersistency | null, override: GraphRunPersistency | null): GraphRunPersistency | null;
29
+ /** Apply a run/work/job persistency override onto a resolved graph contract. */
30
+ export declare function applyGraphRunContractOverride(contract: GraphRunContract, persistencyOverride: unknown): GraphRunContract;
31
+ /** Read graph document version from published graph JSON (`version` or `metadata.version`). */
32
+ export declare function readGraphDocumentVersion(graphDoc: unknown): string | undefined;
33
+ /** Resolve graph-run contract from published graph JSON (GRX-NOTE-001). */
34
+ export declare function resolveGraphRunContract(graphId: string, graphDoc: unknown): GraphRunContract;
35
+ export declare function loadGraphRunContracts(graphIds: string[], graphLoader: {
36
+ loadGraph(graphId: string): Promise<unknown>;
37
+ }): Promise<GraphRunContract[]>;
@@ -0,0 +1,127 @@
1
+ import { DEFAULT_GRAPH_RUN_PERSISTENCY_CONTENT_TYPE } from './persistencyDefaults.js';
2
+ const RELATION_TYPES = new Set(['oneToOne', 'oneToMany', 'manyToOne', 'manyToMany']);
3
+ function asRecord(value) {
4
+ if (!value || typeof value !== 'object' || Array.isArray(value))
5
+ return null;
6
+ return value;
7
+ }
8
+ function readLink(raw) {
9
+ const link = asRecord(raw);
10
+ if (!link)
11
+ return undefined;
12
+ const out = {};
13
+ if (typeof link.relationKey === 'string' && link.relationKey.length > 0) {
14
+ out.relationKey = link.relationKey;
15
+ }
16
+ if (typeof link.fkPath === 'string' && link.fkPath.length > 0) {
17
+ out.fkPath = link.fkPath;
18
+ }
19
+ if (typeof link.type === 'string' && RELATION_TYPES.has(link.type)) {
20
+ out.type = link.type;
21
+ }
22
+ if (typeof link.targetContentType === 'string' && link.targetContentType.length > 0) {
23
+ out.targetContentType = link.targetContentType;
24
+ }
25
+ return Object.keys(out).length > 0 ? out : undefined;
26
+ }
27
+ function normalizePersistency(raw) {
28
+ const entityIdRaw = raw.entityId;
29
+ const entityId = entityIdRaw === 'same-as-input'
30
+ ? 'same-as-input'
31
+ : typeof entityIdRaw === 'string' && entityIdRaw.length > 0
32
+ ? entityIdRaw
33
+ : 'same-as-input';
34
+ const contentTypeRaw = raw.contentType;
35
+ const contentType = typeof contentTypeRaw === 'string' && contentTypeRaw.length > 0
36
+ ? contentTypeRaw
37
+ : DEFAULT_GRAPH_RUN_PERSISTENCY_CONTENT_TYPE;
38
+ const newRecord = raw.newRecord === false ? false : true;
39
+ const link = readLink(raw.link);
40
+ return {
41
+ entityId,
42
+ contentType,
43
+ newRecord,
44
+ ...(link ? { link } : {}),
45
+ };
46
+ }
47
+ /** Parse a work/job/graph persistency object (partial fields allowed). */
48
+ export function parseGraphRunPersistency(raw) {
49
+ const rec = asRecord(raw);
50
+ if (!rec)
51
+ return null;
52
+ return normalizePersistency(rec);
53
+ }
54
+ /** Work/job overrides win over graph defaults; partial overrides merge field-by-field. */
55
+ export function mergeGraphRunPersistency(base, override) {
56
+ if (!override)
57
+ return base;
58
+ if (!base)
59
+ return override;
60
+ return {
61
+ entityId: override.entityId,
62
+ contentType: override.contentType,
63
+ newRecord: override.newRecord !== undefined ? override.newRecord : base.newRecord,
64
+ link: override.link ? { ...base.link, ...override.link } : base.link,
65
+ };
66
+ }
67
+ function mergeRawPersistency(base, rawOverride) {
68
+ // Explicit null on a run/work/job disables result writeback (simulation).
69
+ if (rawOverride === null)
70
+ return null;
71
+ const overrideRec = asRecord(rawOverride);
72
+ if (!overrideRec)
73
+ return base;
74
+ const merged = {
75
+ ...(base
76
+ ? {
77
+ entityId: base.entityId,
78
+ contentType: base.contentType,
79
+ ...(base.newRecord === false ? { newRecord: false } : {}),
80
+ ...(base.link ? { link: { ...base.link } } : {}),
81
+ }
82
+ : {}),
83
+ ...overrideRec,
84
+ };
85
+ const overrideLink = asRecord(overrideRec.link);
86
+ if (base?.link && overrideLink) {
87
+ merged.link = { ...base.link, ...overrideLink };
88
+ }
89
+ return normalizePersistency(merged);
90
+ }
91
+ /** Apply a run/work/job persistency override onto a resolved graph contract. */
92
+ export function applyGraphRunContractOverride(contract, persistencyOverride) {
93
+ return {
94
+ ...contract,
95
+ persistency: mergeRawPersistency(contract.persistency, persistencyOverride),
96
+ };
97
+ }
98
+ function readPersistency(graph) {
99
+ const response = asRecord(graph.response);
100
+ const fromResponse = asRecord(response?.persistency);
101
+ if (fromResponse) {
102
+ return normalizePersistency(fromResponse);
103
+ }
104
+ return null;
105
+ }
106
+ /** Read graph document version from published graph JSON (`version` or `metadata.version`). */
107
+ export function readGraphDocumentVersion(graphDoc) {
108
+ const graph = asRecord(graphDoc) ?? {};
109
+ const metadata = asRecord(graph.metadata);
110
+ if (typeof graph.version === 'string')
111
+ return graph.version;
112
+ if (typeof metadata?.version === 'string')
113
+ return metadata.version;
114
+ return undefined;
115
+ }
116
+ /** Resolve graph-run contract from published graph JSON (GRX-NOTE-001). */
117
+ export function resolveGraphRunContract(graphId, graphDoc) {
118
+ const graph = asRecord(graphDoc) ?? {};
119
+ return {
120
+ graphId,
121
+ persistency: readPersistency(graph),
122
+ graphVersion: readGraphDocumentVersion(graph),
123
+ };
124
+ }
125
+ export async function loadGraphRunContracts(graphIds, graphLoader) {
126
+ return Promise.all(graphIds.map(async (graphId) => resolveGraphRunContract(graphId, await graphLoader.loadGraph(graphId))));
127
+ }
@@ -0,0 +1,2 @@
1
+ /** Default result content type for graph-run persistency. */
2
+ export declare const DEFAULT_GRAPH_RUN_PERSISTENCY_CONTENT_TYPE = "inferences";
@@ -0,0 +1,2 @@
1
+ /** Default result content type for graph-run persistency. */
2
+ export const DEFAULT_GRAPH_RUN_PERSISTENCY_CONTENT_TYPE = 'inferences';
@@ -16,7 +16,7 @@ export type { HostExecuteGraphRunOptions, MainReadinessPolicy, ExecutionStepOpti
16
16
  export type { AiTaskProfileMetadata, AiTaskProfileInputSynthesis, } from './types/aiTaskProfile.js';
17
17
  export { hasWebScopeAuthoring } from './types/aiTaskProfile.js';
18
18
  export type { ExecutionStrategyInvocation, ExecutionStrategyPhase, ExecutionStrategyWrapperKey, SmartInputConfig, TaskStrategyItemData, XynthesizedDestinationScope, XynthesizedMemory, XynthesizedOutputConfig, } from './types/aiTasksDerivedTypes.js';
19
- export type { Graph, GraphModelObject, GraphAiModelConfig, PartialGraphAiModelConfig, GraphModelAliasConfig, GraphRuntimeNodeConfig, TaskNodeRuntimeObject, GraphDocumentMetadata, GraphEntryContract, GraphResponseDefinition, GraphResponseMissingBehavior, GraphResponseSelector, GraphResponseShape, GraphResponseContract, GraphResponseMapping, GraphResponseMappingMissingBehavior, GraphResponseMappingSelector, GraphResponseMappingTarget, GraphExecutionDefaults, GraphExecutionMode, GraphOutputMode, GraphFlowOutline, GraphCoreObjective, GraphNodesResponses, GraphNode, TaskNode, TaskNodeConditions, TaskNodeConditionWhen, TaskNodeJsonCondition, TaskNodeJsConditionFunction, TaskNodeAiCondition, TaskNodeConditionParameters, ModelConfigSelection, ModelConfigCase, TaskNodePureMetadata, TaskNodeExecutionPipelineStep, TaskOutputValidation, FinalizerNode, FinalizerInputBinding, OutputSchema, AggregateFinalizerConfig, BundleFinalizerConfig, SelectFinalizerConfig, SynthesizeFinalizerConfig, UtilityExecutionPolicy, Job, CatalogPlanningKind, CatalogRequestStatus, CatalogBinding, ScopedQuestionCatalogRequestEntry, DiscoveryDefinitionCatalogRequestEntry, DiscoveryDefinitionCatalogRequest, DiscoveryDefinitionPlanningFields, CatalogRequestEntry, StructuredDataFiltersV1, ConditionsDataFilters, } from './types/refs.js';
19
+ export type { Graph, GraphModelObject, GraphAiModelConfig, PartialGraphAiModelConfig, GraphModelAliasConfig, GraphRuntimeNodeConfig, TaskNodeRuntimeObject, GraphDocumentMetadata, GraphEntryContract, GraphResponseDefinition, GraphResponseMissingBehavior, GraphResponseSelector, GraphResponseCoercion, GraphResponseShape, GraphResponseContract, GraphResponseMapping, GraphResponseMappingMissingBehavior, GraphResponseMappingSelector, GraphResponseMappingTarget, GraphExecutionDefaults, GraphExecutionMode, GraphOutputMode, GraphFlowOutline, GraphCoreObjective, GraphNodesResponses, GraphNode, TaskNode, TaskNodeConditions, TaskNodeConditionWhen, TaskNodeJsonCondition, TaskNodeJsConditionFunction, TaskNodeAiCondition, TaskNodeConditionParameters, ModelConfigSelection, ModelConfigCase, TaskNodePureMetadata, TaskNodeExecutionPipelineStep, TaskOutputValidation, FinalizerNode, FinalizerInputBinding, OutputSchema, AggregateFinalizerConfig, BundleFinalizerConfig, SelectFinalizerConfig, SynthesizeFinalizerConfig, UtilityExecutionPolicy, Job, CatalogPlanningKind, CatalogRequestStatus, CatalogBinding, ScopedQuestionCatalogRequestEntry, DiscoveryDefinitionCatalogRequestEntry, DiscoveryDefinitionCatalogRequest, DiscoveryDefinitionPlanningFields, CatalogRequestEntry, StructuredDataFiltersV1, ConditionsDataFilters, } from './types/refs.js';
20
20
  export { mergeGraphDocumentModel, EXELLIX_GRAPH_MODEL_VARIABLE_KEY, EXELLIX_STRUCTURED_DATA_FILTERS_V1, } from './types/refs.js';
21
21
  export type { TaskNodeTaskConfiguration, TaskNodeScopedDataReaderPackSlot, } from './types/taskNodeConfiguration.js';
22
22
  export { getTaskConfiguration } from './types/taskNodeConfiguration.js';
@@ -79,7 +79,7 @@ export type { BuildTaskNodeRunTaskRequestArgs, BuildTaskNodeRunTaskRequestResult
79
79
  /** Re-exported from `@exellix/ai-tasks` — preflight validation without `runTask`. */
80
80
  export { validateRunTaskConfig, validateRunTaskInvoke, analyzeExpectedRunTaskInput, checkExpectedInputAgainstRequest, collectSmartInputValidationIssues, buildRunTaskValidationContext, validateParsedOutput, listTokens, analyzeTemplateResolution, renderSmartInput, extractTokenNamesFromStrings, getSkillTokens, getSkillContent, } from '@exellix/ai-tasks';
81
81
  export type { RunTaskValidationIssue, RunTaskValidationResult, RunTaskInvokeValidationResult, ExpectedRunTaskInputPath, AnalyzeExpectedRunTaskInputResult, ValidateRunTaskInvokeParams, RunTaskTemplateResolver, OutputValidationResult, } from '@exellix/ai-tasks';
82
- export { buildExellixGraphRuntimeObjects, setRuntimeObjectsLastJobId, summarizeRuntimeObjectsForPlayground, EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME, EXELLIX_AI_TASKS_PACKAGE_NAME, } from './runtime/runtimeObjects.js';
82
+ export { buildExellixGraphRuntimeObjects, setRuntimeObjectsLastJobId, summarizeRuntimeObjectsForPlayground, X12I_GRAPH_RUNTIME_PACKAGE_NAME, X12I_AI_TASKS_PACKAGE_NAME, } from './runtime/runtimeObjects.js';
83
83
  export type { ActivixQueryableClient, LogxerQueryableClient, LogxerLogLine, PackageRuntimeObjects, RuntimeObjects, BuildExellixGraphRuntimeObjectsInput, RuntimeObjectsObservabilitySummary, } from './runtime/runtimeObjects.js';
84
84
  export type { GetJobLogsInput, GetJobLogsResult, QueryableLogLine } from '@x12i/logxer';
85
85
  export { assertCanonicalGraphDocument, assertCanonicalTaskNode, getCanonicalGraphDocumentViolations, CANONICAL_GRAPH_TOP_LEVEL_KEYS, } from './runtime/validateCanonicalGraphDocument.js';
@@ -95,6 +95,13 @@ export type { ExecutableGraphPlanV2, NodeExecutionPlan, ExecutionUnitPlanV2, } f
95
95
  export type { AuthoringGraphDocument } from '@x12i/graphenix-executable-contracts';
96
96
  export { compileExellixExecutablePlan } from './compile/compileExellixExecutablePlan.js';
97
97
  export type { CompileExellixExecutablePlanOptions } from './compile/compileExellixExecutablePlan.js';
98
+ export { applyGraphResponseDefinition, resolveGraphResponse, assessGraphResponseCompleteness, GRAPH_RESPONSE_INCOMPLETE, } from './runtime/graphResponseMapping.js';
99
+ export type { GraphResponseMappingContext, GraphResponseMappingError } from './runtime/graphResponseMapping.js';
100
+ export { coerceStringArray } from './runtime/coerceStringArray.js';
101
+ export { validateGraphResponseWiring, assertGraphResponseWiringOk, RESPONSE_PATH_NO_WRITER, TASK_MAP_TARGET_INVALID, TASK_MAP_SOURCE_INVALID, RESPONSE_LEGACY_SHAPE_KEY, EXECUTION_MAPPING_LEGACY_PATH, RESPONSE_EMPTY_SHAPE, RESPONSE_LEGACY_PRESET_ID, RESPONSE_COERCE_STRING_ARRAY_ON_SCALAR, } from './validation/validateGraphResponseWiring.js';
102
+ export type { GraphResponseWiringIssue, GraphResponseWiringValidation, } from './validation/validateGraphResponseWiring.js';
103
+ export { detectAuthoringResponseSourceIssues, resolveAuthoringGraphResponse, migrateLegacyGraphResponseToAuthoring, GRAPH_RESPONSE_LEGACY_SOURCE, GRAPH_RESPONSE_DUAL_SOURCE, } from './validation/authoringGraphResponse.js';
104
+ export type { AuthoringResponseSourceIssue } from './validation/authoringGraphResponse.js';
98
105
  export { buildGraphExecutionRequestFromStudioExecute as buildAuthoringStudioGraphExecutionRequest } from '@x12i/graphenix-execute-envelope';
99
106
  export type { RunTaskRequest, RunTaskResponse, TasksClientLike, GraphLoader as RuntimeGraphLoader, ExecuteGraphInput, GraphExecutionRequest, GraphRuntimeObject, GraphKnowledgeResolver, GraphKnowledgeResolverContext, ExecuteGraphResult, ExellixGraphRuntimeOptions, } from './runtime/ExellixGraphRuntime.js';
100
107
  export type { PlanStatus, GraphPlan, GraphEngine, GraphEngineFactory, } from './runtime/GraphEngine.js';
@@ -120,5 +127,7 @@ export type { CreatePlaygroundReporterOptions, PlaygroundDebugArtifact, Playgrou
120
127
  export { getNodeScopingQuestion, getNodeMemoryShape, getNodeNarrixDiscovery, fetchNodeScopingData, inspectNode, resolveNodeSkillKey, getGraphNodes, getGraphCatalogs, inspectGraph, collectPredicatePaths, executionMemoryPathTail, executionMemoryTailsMatch, getNodeExecutionMemoryWriteTails, getNodeControlDependencies, getNodeSideEffects, inspectFinalizer, inspectNodeContract, inspectGraphContracts, EXELLIX_VIRTUAL_GRAPH_ENTRY_NODE_ID, EXELLIX_VIRTUAL_GRAPH_RESPONSE_NODE_ID, EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY, EXELLIX_VIRTUAL_BOUNDARY_EDGE_FLAG_KEY, getVirtualGraphEntryLayer, getVirtualGraphResponseLayer, materializeVirtualBoundaryDiagram, stripMaterializedVirtualBoundaryDiagram, validateCatalogPlanning, isCatalogBinding, isCatalogRequestEntry, inspectGraphModelSelection, } from './inspection/index.js';
121
128
  export type { GraphModelSelectionInspection, NodeModelSelection, ModelSelectionSource, NodeScopingQuestion, NodeScopeSource, NodeScopeTarget, NodeScopingData, NodeMemoryShape, MemoryPath, NodeNarrixDiscovery, NodeInspection, GraphInspection, NodeIOEdge, GraphCatalogs, CatalogBindingSummary, CatalogPlanningValidationIssue, GraphVirtualIO, GraphVirtualIONode, GraphVirtualIOEdge, GraphVirtualIONodeRole, VirtualGraphEntryLayer, VirtualGraphResponseLayer, MaterializeVirtualBoundaryDiagramOptions, MaterializedVirtualBoundaryDiagram, MaterializedVirtualBoundaryDiagramMeta, InspectNodeContractOptions, LocalSkillKey, NodeExecutionType, FactProvenance, ProvenancedSection, NodeInputBindingEntry, ScopedDataDependency, NodeInputContract, ExecutionMemoryWriteSpec, NodeOutputContract, NodeSideEffects, ControlBranchEntry, NodeControlContract, NodeValidationContract, FinalizerContractInspection, NodeContractInspection, GraphContractsInspection, } from './inspection/index.js';
122
129
  export type { NarrixPreProcessorConfig, NarrixModeKey, NarrixRunInput, NarrixScope, ResolvedNarrixWirePayload, LocalTaskHandler, LocalTaskContext, } from './types/narrix.js';
130
+ export { resolveGraphRunContract, loadGraphRunContracts, readGraphDocumentVersion, parseGraphRunPersistency, mergeGraphRunPersistency, applyGraphRunContractOverride, type GraphRunContract, type GraphRunPersistency, type GraphRunPersistencyLink, } from './contract/graphRunContract.js';
131
+ export { DEFAULT_GRAPH_RUN_PERSISTENCY_CONTENT_TYPE } from './contract/persistencyDefaults.js';
123
132
  export { shutdownMemorixRuntime } from './runtime/localSkills/memorixRuntime.js';
124
133
  export type { ScopedDataReaderConfig, ScopedDataReaderOutput, ScopedDataReaderPackOutput, ScopedAnswerAssemblerConfig, ScopedAnswerAssemblerOutput, ScopedAnswerWriterConfig, ScopedAnswerWriterOutput, } from './runtime/localSkills/index.js';
package/dist/src/index.js CHANGED
@@ -60,7 +60,7 @@ export { runTaskResponseSucceeded } from './runtime/runTaskResponse.js';
60
60
  export { buildTaskNodeRunTaskRequest, validateTaskNodeRunTaskConfig, validateTaskNodeRunTaskInvoke, } from './runtime/taskNodeRunTaskPreflight.js';
61
61
  /** Re-exported from `@exellix/ai-tasks` — preflight validation without `runTask`. */
62
62
  export { validateRunTaskConfig, validateRunTaskInvoke, analyzeExpectedRunTaskInput, checkExpectedInputAgainstRequest, collectSmartInputValidationIssues, buildRunTaskValidationContext, validateParsedOutput, listTokens, analyzeTemplateResolution, renderSmartInput, extractTokenNamesFromStrings, getSkillTokens, getSkillContent, } from '@exellix/ai-tasks';
63
- export { buildExellixGraphRuntimeObjects, setRuntimeObjectsLastJobId, summarizeRuntimeObjectsForPlayground, EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME, EXELLIX_AI_TASKS_PACKAGE_NAME, } from './runtime/runtimeObjects.js';
63
+ export { buildExellixGraphRuntimeObjects, setRuntimeObjectsLastJobId, summarizeRuntimeObjectsForPlayground, X12I_GRAPH_RUNTIME_PACKAGE_NAME, X12I_AI_TASKS_PACKAGE_NAME, } from './runtime/runtimeObjects.js';
64
64
  export { assertCanonicalGraphDocument, assertCanonicalTaskNode, getCanonicalGraphDocumentViolations, CANONICAL_GRAPH_TOP_LEVEL_KEYS, } from './runtime/validateCanonicalGraphDocument.js';
65
65
  export { assertCanonicalGraphRuntimeObject } from './runtime/validateCanonicalGraphRuntime.js';
66
66
  export { GRAPH_ENTRY_STUDIO_ONLY_KEYS, GRAPH_METADATA_STUDIO_ONLY_KEYS, stripGraphModelStudioFields, primaryRuntimeInputFromStudioDocument, getGraphEntryStudioOnlyKeyViolations, getGraphMetadataStudioOnlyKeyViolations, } from './runtime/graphModelStudioSeparation.js';
@@ -69,6 +69,10 @@ export { computeGraphDocumentContentSha256, stableStringifyGraphDocument, } from
69
69
  // New runtime with injection seam
70
70
  export { createExellixGraphRuntime } from './runtime/ExellixGraphRuntime.js';
71
71
  export { compileExellixExecutablePlan } from './compile/compileExellixExecutablePlan.js';
72
+ export { applyGraphResponseDefinition, resolveGraphResponse, assessGraphResponseCompleteness, GRAPH_RESPONSE_INCOMPLETE, } from './runtime/graphResponseMapping.js';
73
+ export { coerceStringArray } from './runtime/coerceStringArray.js';
74
+ export { validateGraphResponseWiring, assertGraphResponseWiringOk, RESPONSE_PATH_NO_WRITER, TASK_MAP_TARGET_INVALID, TASK_MAP_SOURCE_INVALID, RESPONSE_LEGACY_SHAPE_KEY, EXECUTION_MAPPING_LEGACY_PATH, RESPONSE_EMPTY_SHAPE, RESPONSE_LEGACY_PRESET_ID, RESPONSE_COERCE_STRING_ARRAY_ON_SCALAR, } from './validation/validateGraphResponseWiring.js';
75
+ export { detectAuthoringResponseSourceIssues, resolveAuthoringGraphResponse, migrateLegacyGraphResponseToAuthoring, GRAPH_RESPONSE_LEGACY_SOURCE, GRAPH_RESPONSE_DUAL_SOURCE, } from './validation/authoringGraphResponse.js';
72
76
  export { buildGraphExecutionRequestFromStudioExecute as buildAuthoringStudioGraphExecutionRequest } from '@x12i/graphenix-execute-envelope';
73
77
  // Testkit (in-memory loader, dep engine, recording client — sample NARRIX tasks: `@exellix/graph-engine/testkit`)
74
78
  export { InMemoryGraphLoader, DepGraphEngineFactory } from '../testkit/index.js';
@@ -88,5 +92,8 @@ export { composeEventEmitters } from './runtime/events.js';
88
92
  export { createPlaygroundReporter } from './playground/index.js';
89
93
  // Graph Inspection API
90
94
  export { getNodeScopingQuestion, getNodeMemoryShape, getNodeNarrixDiscovery, fetchNodeScopingData, inspectNode, resolveNodeSkillKey, getGraphNodes, getGraphCatalogs, inspectGraph, collectPredicatePaths, executionMemoryPathTail, executionMemoryTailsMatch, getNodeExecutionMemoryWriteTails, getNodeControlDependencies, getNodeSideEffects, inspectFinalizer, inspectNodeContract, inspectGraphContracts, EXELLIX_VIRTUAL_GRAPH_ENTRY_NODE_ID, EXELLIX_VIRTUAL_GRAPH_RESPONSE_NODE_ID, EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY, EXELLIX_VIRTUAL_BOUNDARY_EDGE_FLAG_KEY, getVirtualGraphEntryLayer, getVirtualGraphResponseLayer, materializeVirtualBoundaryDiagram, stripMaterializedVirtualBoundaryDiagram, validateCatalogPlanning, isCatalogBinding, isCatalogRequestEntry, inspectGraphModelSelection, } from './inspection/index.js';
95
+ // Graph-run contract (GRX-NOTE-001) — persistency intent from published graph JSON
96
+ export { resolveGraphRunContract, loadGraphRunContracts, readGraphDocumentVersion, parseGraphRunPersistency, mergeGraphRunPersistency, applyGraphRunContractOverride, } from './contract/graphRunContract.js';
97
+ export { DEFAULT_GRAPH_RUN_PERSISTENCY_CONTENT_TYPE } from './contract/persistencyDefaults.js';
91
98
  export { shutdownMemorixRuntime } from './runtime/localSkills/memorixRuntime.js';
92
99
  // Web context rendering — consume execution.webContext → markdown for prompts (Layer 04)
@@ -462,6 +462,10 @@ export function inspectGraph(graph) {
462
462
  ioEdges,
463
463
  catalogs,
464
464
  graphDocumentModel: mergeGraphDocumentModel(graph),
465
+ response: {
466
+ source: 'graph.response',
467
+ definition: graph.response,
468
+ },
465
469
  virtualIO,
466
470
  };
467
471
  }
@@ -264,6 +264,11 @@ export type GraphInspection = {
264
264
  * Graph `metadata` (same shape as `variables.__graphModel` during execution).
265
265
  */
266
266
  graphDocumentModel: GraphDocumentMetadata;
267
+ /** Executable response definition source — always `graph.response` in 9.x. */
268
+ response: {
269
+ source: 'graph.response';
270
+ definition: import('../types/refs.js').GraphResponseDefinition;
271
+ };
267
272
  /**
268
273
  * Virtual Layer 01 / 08 nodes and edges for visualization; not part of executable `nodes` / `edges`.
269
274
  */
@@ -1,4 +1,4 @@
1
- import { EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME } from '../runtime/runtimeObjects.js';
1
+ import { X12I_GRAPH_RUNTIME_PACKAGE_NAME } from '../runtime/runtimeObjects.js';
2
2
  /** Recommended Mongo indexes for exellix-graph Activix streams (see `@x12i/activix` README). */
3
3
  export const ACTIVIX_EXELLIX_RUN_CONTEXT_INDEXES = [
4
4
  { keys: { 'runContext.jobId': 1 } },
@@ -55,5 +55,5 @@ export function buildNodeActivixRunContext(args) {
55
55
  };
56
56
  }
57
57
  export function activixExellixCollectionRegistryOwner() {
58
- return { package: EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME };
58
+ return { package: X12I_GRAPH_RUNTIME_PACKAGE_NAME };
59
59
  }
@@ -1,4 +1,5 @@
1
1
  import { getFinalizerNodeConfig, getTaskNodeConfig, isExecutableFinalizerNode, isExecutableTaskNode, } from '@x12i/graphenix-executable-contracts';
2
+ import { GRAPH_RESPONSE_LEGACY_SOURCE } from '../validation/authoringGraphResponse.js';
2
3
  function isPlainRecord(v) {
3
4
  return v != null && typeof v === 'object' && !Array.isArray(v);
4
5
  }
@@ -82,24 +83,40 @@ function edgesFromPlan(plan) {
82
83
  });
83
84
  }
84
85
  function responseFromEmbedded(doc, plan) {
85
- const metaResponse = doc.graph.metadata?.graphResponse;
86
- if (isPlainRecord(metaResponse)) {
86
+ const graphRecord = doc.graph;
87
+ const graphLevel = graphRecord.response;
88
+ if (isPlainRecord(graphLevel) && 'shape' in graphLevel) {
87
89
  const out = {
88
- shape: isPlainRecord(metaResponse.shape) ? structuredClone(metaResponse.shape) : {},
90
+ shape: isPlainRecord(graphLevel.shape) || Array.isArray(graphLevel.shape)
91
+ ? structuredClone(graphLevel.shape)
92
+ : graphLevel.shape ?? {},
89
93
  };
90
- if (metaResponse.missing !== undefined) {
91
- out.missing = metaResponse.missing;
92
- }
93
- if (metaResponse.version !== undefined) {
94
- out.version = metaResponse.version;
94
+ if (graphLevel.missing !== undefined) {
95
+ out.missing = graphLevel.missing;
95
96
  }
96
97
  return out;
97
98
  }
99
+ const metaResponse = doc.graph.metadata?.graphResponse;
100
+ if (isPlainRecord(metaResponse) && hasExecutableMetadataResponse(metaResponse)) {
101
+ const err = new Error('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.');
102
+ err.code = GRAPH_RESPONSE_LEGACY_SOURCE;
103
+ throw err;
104
+ }
98
105
  if (isPlainRecord(plan.contracts?.response?.finalOutputSchema)) {
99
106
  return { shape: { type: 'literal', value: {} } };
100
107
  }
101
108
  return { shape: {} };
102
109
  }
110
+ function hasExecutableMetadataResponse(metaResponse) {
111
+ const shape = metaResponse.shape;
112
+ if (shape === undefined)
113
+ return false;
114
+ if (isPlainRecord(shape))
115
+ return Object.keys(shape).length > 0;
116
+ if (Array.isArray(shape))
117
+ return shape.length > 0;
118
+ return true;
119
+ }
103
120
  /** Materialize exellix {@link Graph} from plan embedded normalized authoring graph. */
104
121
  export function embeddedGraphToExellixGraph(plan) {
105
122
  if (plan.normalizedGraph.mode !== 'embedded') {
@@ -100,8 +100,8 @@ export interface ExecuteGraphResult {
100
100
  nodeId?: string;
101
101
  error: any;
102
102
  }>;
103
- /** Final execution object (includes `_trace.nodes` for per-node timing). */
104
- execution?: unknown;
103
+ /** Final execution object (includes `_trace.nodes` for per-node timing). Always present on graph runs (FR-GE-005). */
104
+ execution: Record<string, unknown>;
105
105
  /** Final graph-engine-owned output memory accumulated from finalizer `outputMapping` writes. */
106
106
  outputsMemory?: unknown;
107
107
  runLog?: RunLogEntry[];
@@ -19,7 +19,7 @@ import { assertFinalizerRequiredReadsResolvable, validateGraphFinalizer, } from
19
19
  import { executeDeterministicFinalizer, executeSynthesizeFinalizer } from "./finalizers/executeFinalizer.js";
20
20
  import { createFinalizerError } from "./finalizers/errors.js";
21
21
  import { assertCanonicalGraphRuntimeObject } from "./validateCanonicalGraphRuntime.js";
22
- import { applyGraphResponseDefinition } from "./graphResponseMapping.js";
22
+ import { applyGraphResponseDefinition, assessGraphResponseCompleteness, GRAPH_RESPONSE_INCOMPLETE } from "./graphResponseMapping.js";
23
23
  import { buildAiTasksObservabilityRecord } from "./aiTasksObservability.js";
24
24
  import { buildRunTaskIdentityEnvelope, shouldForwardRunTaskTraceMode, } from "./runTaskAugments.js";
25
25
  import { buildRunLog, extractLogxerCorrelationFromMetadata, extractTaskRunLogFromMetadata, resolveRunLogLimits, } from "./buildRunLog.js";
@@ -1172,7 +1172,7 @@ export function createExellixGraphRuntime(opts) {
1172
1172
  logxerCorrelationId: logxerCorrelationIdLast,
1173
1173
  });
1174
1174
  return {
1175
- execution: currentExecution,
1175
+ execution: (isPlainRecord(currentExecution) ? currentExecution : {}),
1176
1176
  outputsMemory: currentOutputsMemory,
1177
1177
  ...built,
1178
1178
  };
@@ -1256,7 +1256,7 @@ export function createExellixGraphRuntime(opts) {
1256
1256
  const executionMemoryForResponse = isPlainRecord(currentExecution)
1257
1257
  ? structuredClone(currentExecution)
1258
1258
  : currentExecution;
1259
- return applyGraphResponseDefinition({
1259
+ const finalOutput = applyGraphResponseDefinition({
1260
1260
  response: graph.response,
1261
1261
  context: {
1262
1262
  graph,
@@ -1268,6 +1268,38 @@ export function createExellixGraphRuntime(opts) {
1268
1268
  stepsResponses,
1269
1269
  },
1270
1270
  });
1271
+ const completeness = assessGraphResponseCompleteness(graph.response, finalOutput);
1272
+ if (completeness.incomplete) {
1273
+ return {
1274
+ responseErrors: [
1275
+ {
1276
+ code: completeness.code ?? GRAPH_RESPONSE_INCOMPLETE,
1277
+ message: completeness.message ?? 'graph.response resolved empty.',
1278
+ },
1279
+ ],
1280
+ };
1281
+ }
1282
+ return finalOutput !== undefined ? { finalOutput } : {};
1283
+ }
1284
+ function resolveGraphRunFinalOutput(baseErrors = errors) {
1285
+ const resolved = buildFinalOutputFromGraphResponse();
1286
+ if (resolved.responseErrors?.length) {
1287
+ return {
1288
+ errors: [
1289
+ ...baseErrors,
1290
+ ...resolved.responseErrors.map((re) => {
1291
+ const e = new Error(re.message);
1292
+ e.code = re.code;
1293
+ return { error: e };
1294
+ }),
1295
+ ],
1296
+ responseFailed: true,
1297
+ };
1298
+ }
1299
+ return {
1300
+ finalOutput: resolved.finalOutput,
1301
+ errors: baseErrors,
1302
+ };
1271
1303
  }
1272
1304
  const playgroundReporter = runtime.playgroundReporter ?? opts.playgroundReporter;
1273
1305
  if (playgroundReporter) {
@@ -1314,7 +1346,7 @@ export function createExellixGraphRuntime(opts) {
1314
1346
  taskId: graphTaskId,
1315
1347
  error: err,
1316
1348
  });
1317
- const finalOutput = buildFinalOutputFromGraphResponse();
1349
+ const responseResolution = resolveGraphRunFinalOutput([...errors, { error: err }]);
1318
1350
  appendExecutionEvent(executionTrace, {
1319
1351
  id: `evt:${graphTaskId}:graph.failed`,
1320
1352
  ts: new Date().toISOString(),
@@ -1330,8 +1362,8 @@ export function createExellixGraphRuntime(opts) {
1330
1362
  outputsByNodeId,
1331
1363
  stepsResponses,
1332
1364
  engineSnapshot: engine.snapshot(),
1333
- ...(finalOutput !== undefined ? { finalOutput } : {}),
1334
- errors: [...errors, { error: err }],
1365
+ ...(responseResolution.finalOutput !== undefined ? { finalOutput: responseResolution.finalOutput } : {}),
1366
+ errors: responseResolution.errors,
1335
1367
  planAudit,
1336
1368
  trace: executionTrace,
1337
1369
  ...finalizeGraphPayload("failed"),
@@ -1350,8 +1382,8 @@ export function createExellixGraphRuntime(opts) {
1350
1382
  while (true) {
1351
1383
  const wavePlan = engine.plan();
1352
1384
  if (wavePlan.status === "completed") {
1353
- const graphStatus = errors.length ? "failed" : "completed";
1354
- const finalOutput = buildFinalOutputFromGraphResponse();
1385
+ const responseResolution = resolveGraphRunFinalOutput();
1386
+ const graphStatus = errors.length || responseResolution.responseFailed ? "failed" : "completed";
1355
1387
  appendExecutionEvent(executionTrace, {
1356
1388
  id: `evt:${graphTaskId}:graph.${graphStatus}`,
1357
1389
  ts: new Date().toISOString(),
@@ -1360,6 +1392,7 @@ export function createExellixGraphRuntime(opts) {
1360
1392
  message: `Graph execution ${graphStatus}.`,
1361
1393
  });
1362
1394
  validateExecutionTrace(executionTrace, plan);
1395
+ const resultErrors = responseResolution.errors.length ? responseResolution.errors : undefined;
1363
1396
  const result = {
1364
1397
  jobId,
1365
1398
  taskId: graphTaskId,
@@ -1368,9 +1401,9 @@ export function createExellixGraphRuntime(opts) {
1368
1401
  outputsByNodeId,
1369
1402
  stepsResponses,
1370
1403
  engineSnapshot: engine.snapshot(),
1371
- ...(finalOutput !== undefined ? { finalOutput } : {}),
1372
- ...(errors.length === 0 ? { finalizerNodeId, finalizerType: finalizer.finalizerType } : {}),
1373
- errors: errors.length ? errors : undefined,
1404
+ ...(responseResolution.finalOutput !== undefined ? { finalOutput: responseResolution.finalOutput } : {}),
1405
+ ...(graphStatus === "completed" ? { finalizerNodeId, finalizerType: finalizer.finalizerType } : {}),
1406
+ errors: resultErrors,
1374
1407
  planAudit,
1375
1408
  trace: executionTrace,
1376
1409
  ...finalizeGraphPayload(graphStatus),
@@ -1381,14 +1414,14 @@ export function createExellixGraphRuntime(opts) {
1381
1414
  if (eventEmitter) {
1382
1415
  if (graphStatus === "completed") {
1383
1416
  eventEmitter.emit(createGraphCompleteEvent(jobId, resolvedGraphId, graphTaskId, {
1384
- output: finalOutput,
1417
+ output: responseResolution.finalOutput,
1385
1418
  nodesExecuted: Object.keys(outputsByNodeId).length,
1386
1419
  finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
1387
1420
  ...(jobCorrelation ?? {}),
1388
1421
  }));
1389
1422
  }
1390
1423
  else {
1391
- eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, errors[0]?.error, {
1424
+ eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, responseResolution.errors[0]?.error ?? errors[0]?.error, {
1392
1425
  finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
1393
1426
  ...(jobCorrelation ?? {}),
1394
1427
  }));
@@ -1399,7 +1432,7 @@ export function createExellixGraphRuntime(opts) {
1399
1432
  if (wavePlan.status !== "continue") {
1400
1433
  const err = new Error(`GRAPH_BLOCKED: status=${wavePlan.status}`);
1401
1434
  err.code = "GRAPH_BLOCKED";
1402
- const finalOutput = buildFinalOutputFromGraphResponse();
1435
+ const responseResolution = resolveGraphRunFinalOutput([...errors, { error: err }]);
1403
1436
  const result = {
1404
1437
  jobId,
1405
1438
  taskId: graphTaskId,
@@ -1408,8 +1441,8 @@ export function createExellixGraphRuntime(opts) {
1408
1441
  outputsByNodeId,
1409
1442
  stepsResponses,
1410
1443
  engineSnapshot: engine.snapshot(),
1411
- ...(finalOutput !== undefined ? { finalOutput } : {}),
1412
- errors: [...errors, { error: err }],
1444
+ ...(responseResolution.finalOutput !== undefined ? { finalOutput: responseResolution.finalOutput } : {}),
1445
+ errors: responseResolution.errors,
1413
1446
  planAudit,
1414
1447
  trace: executionTrace,
1415
1448
  ...finalizeGraphPayload("failed"),
@@ -1651,7 +1684,7 @@ export function createExellixGraphRuntime(opts) {
1651
1684
  throw e;
1652
1685
  });
1653
1686
  if (failFast && errors.length) {
1654
- const finalOutput = buildFinalOutputFromGraphResponse();
1687
+ const responseResolution = resolveGraphRunFinalOutput();
1655
1688
  const result = {
1656
1689
  jobId,
1657
1690
  taskId: graphTaskId,
@@ -1660,8 +1693,8 @@ export function createExellixGraphRuntime(opts) {
1660
1693
  outputsByNodeId,
1661
1694
  stepsResponses,
1662
1695
  engineSnapshot: engine.snapshot(),
1663
- ...(finalOutput !== undefined ? { finalOutput } : {}),
1664
- errors,
1696
+ ...(responseResolution.finalOutput !== undefined ? { finalOutput: responseResolution.finalOutput } : {}),
1697
+ errors: responseResolution.errors,
1665
1698
  planAudit,
1666
1699
  trace: executionTrace,
1667
1700
  ...finalizeGraphPayload("failed"),