@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,18 @@
|
|
|
1
|
+
import type { OutputSchema } from '../../types/refs.js';
|
|
2
|
+
export type SchemaValidationError = {
|
|
3
|
+
path: string;
|
|
4
|
+
expected: string;
|
|
5
|
+
actual: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function validateOutputSchemaShape(schema: unknown): {
|
|
8
|
+
ok: true;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function validateAgainstOutputSchema(output: unknown, schema: OutputSchema): {
|
|
14
|
+
ok: true;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
errors: SchemaValidationError[];
|
|
18
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
function isPlainObject(v) {
|
|
2
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
3
|
+
}
|
|
4
|
+
export function validateOutputSchemaShape(schema) {
|
|
5
|
+
if (schema == null)
|
|
6
|
+
return { ok: true };
|
|
7
|
+
if (!isPlainObject(schema))
|
|
8
|
+
return { ok: false, message: 'outputSchema must be an object schema' };
|
|
9
|
+
const s = schema;
|
|
10
|
+
if (s.type !== 'object')
|
|
11
|
+
return { ok: false, message: 'outputSchema.type must be "object"' };
|
|
12
|
+
if (s.required != null && !Array.isArray(s.required))
|
|
13
|
+
return { ok: false, message: 'outputSchema.required must be an array of strings' };
|
|
14
|
+
if (s.properties != null && !isPlainObject(s.properties))
|
|
15
|
+
return { ok: false, message: 'outputSchema.properties must be an object' };
|
|
16
|
+
if (s.additionalProperties != null && typeof s.additionalProperties !== 'boolean') {
|
|
17
|
+
return { ok: false, message: 'outputSchema.additionalProperties must be boolean' };
|
|
18
|
+
}
|
|
19
|
+
return { ok: true };
|
|
20
|
+
}
|
|
21
|
+
export function validateAgainstOutputSchema(output, schema) {
|
|
22
|
+
const errors = [];
|
|
23
|
+
if (!isPlainObject(output)) {
|
|
24
|
+
errors.push({
|
|
25
|
+
path: '$',
|
|
26
|
+
expected: 'object',
|
|
27
|
+
actual: Array.isArray(output) ? 'array' : output === null ? 'null' : typeof output,
|
|
28
|
+
});
|
|
29
|
+
return { ok: false, errors };
|
|
30
|
+
}
|
|
31
|
+
const out = output;
|
|
32
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
33
|
+
for (const k of required) {
|
|
34
|
+
if (out[k] === undefined) {
|
|
35
|
+
errors.push({ path: `$.${k}`, expected: 'present', actual: 'missing' });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const props = schema.properties ?? {};
|
|
39
|
+
for (const [k, def] of Object.entries(props)) {
|
|
40
|
+
if (out[k] === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
const expected = def?.type;
|
|
43
|
+
if (!expected)
|
|
44
|
+
continue;
|
|
45
|
+
const v = out[k];
|
|
46
|
+
const actual = Array.isArray(v) ? 'array' : v === null ? 'null' : typeof v === 'object' ? 'object' : typeof v;
|
|
47
|
+
const ok = (expected === 'string' && typeof v === 'string') ||
|
|
48
|
+
(expected === 'number' && typeof v === 'number') ||
|
|
49
|
+
(expected === 'boolean' && typeof v === 'boolean') ||
|
|
50
|
+
(expected === 'array' && Array.isArray(v)) ||
|
|
51
|
+
(expected === 'object' && isPlainObject(v));
|
|
52
|
+
if (!ok)
|
|
53
|
+
errors.push({ path: `$.${k}`, expected, actual });
|
|
54
|
+
}
|
|
55
|
+
if (schema.additionalProperties === false) {
|
|
56
|
+
const allowed = new Set(Object.keys(props));
|
|
57
|
+
for (const k of Object.keys(out)) {
|
|
58
|
+
if (!allowed.has(k))
|
|
59
|
+
errors.push({ path: `$.${k}`, expected: 'not allowed', actual: 'present' });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return errors.length ? { ok: false, errors } : { ok: true };
|
|
63
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FinalizerNode, Graph } from '../../types/refs.js';
|
|
2
|
+
export type RequiredFinalizerExecutionMemoryRead = {
|
|
3
|
+
memory: 'execution' | 'outputs';
|
|
4
|
+
path: string;
|
|
5
|
+
sources: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function collectRequiredFinalizerExecutionMemoryReads(finalizer: FinalizerNode, graph?: Graph): RequiredFinalizerExecutionMemoryRead[];
|
|
8
|
+
export declare function assertFinalizerRequiredReadsResolvable(args: {
|
|
9
|
+
graph: Graph;
|
|
10
|
+
finalizer: FinalizerNode;
|
|
11
|
+
executionMemory: unknown;
|
|
12
|
+
outputsMemory?: unknown;
|
|
13
|
+
}): void;
|
|
14
|
+
export declare function validateGraphFinalizer(graph: Graph): {
|
|
15
|
+
finalizer: FinalizerNode;
|
|
16
|
+
};
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { createFinalizerError } from './errors.js';
|
|
2
|
+
import { validateOutputSchemaShape } from './schema.js';
|
|
3
|
+
function asArrayNodes(graph) {
|
|
4
|
+
return Array.isArray(graph.nodes) ? graph.nodes : Object.values(graph.nodes ?? {});
|
|
5
|
+
}
|
|
6
|
+
function isPlainObject(v) {
|
|
7
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
8
|
+
}
|
|
9
|
+
function getByDotPath(root, path) {
|
|
10
|
+
if (!path)
|
|
11
|
+
return root;
|
|
12
|
+
const parts = path.split('.').filter(Boolean);
|
|
13
|
+
let cur = root;
|
|
14
|
+
for (const p of parts) {
|
|
15
|
+
if (cur == null)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (typeof cur !== 'object')
|
|
18
|
+
return undefined;
|
|
19
|
+
cur = cur[p];
|
|
20
|
+
}
|
|
21
|
+
return cur;
|
|
22
|
+
}
|
|
23
|
+
function memoryPathsMatch(a, b) {
|
|
24
|
+
return a === b || a.startsWith(b + '.') || b.startsWith(a + '.');
|
|
25
|
+
}
|
|
26
|
+
function pushRequiredRead(reads, memory, path, source, optional) {
|
|
27
|
+
if (optional === true || typeof path !== 'string' || path.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
const existing = reads.find((read) => read.memory === memory && read.path === path);
|
|
30
|
+
if (existing) {
|
|
31
|
+
if (!existing.sources.includes(source))
|
|
32
|
+
existing.sources.push(source);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
reads.push({ memory, path, sources: [source] });
|
|
36
|
+
}
|
|
37
|
+
function getNodeById(graph, nodeId) {
|
|
38
|
+
return asArrayNodes(graph).find((n) => String(n?.id) === String(nodeId));
|
|
39
|
+
}
|
|
40
|
+
export function collectRequiredFinalizerExecutionMemoryReads(finalizer, graph) {
|
|
41
|
+
const reads = [];
|
|
42
|
+
for (const [name, binding] of Object.entries(finalizer.inputs ?? {})) {
|
|
43
|
+
if (binding.type === 'executionMemoryPath') {
|
|
44
|
+
pushRequiredRead(reads, 'execution', binding.path, `inputs.${name}`, binding.optional);
|
|
45
|
+
}
|
|
46
|
+
else if (binding.type === 'outputsMemoryPath') {
|
|
47
|
+
pushRequiredRead(reads, 'outputs', binding.path, `inputs.${name}`, binding.optional);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const cfg = finalizer.config;
|
|
51
|
+
if (finalizer.finalizerType !== 'aggregate' || !isPlainObject(cfg)) {
|
|
52
|
+
return reads;
|
|
53
|
+
}
|
|
54
|
+
if (cfg.strategy === 'report-schema' && isPlainObject(cfg.sections)) {
|
|
55
|
+
for (const [key, spec] of Object.entries(cfg.sections)) {
|
|
56
|
+
if (!isPlainObject(spec))
|
|
57
|
+
continue;
|
|
58
|
+
pushRequiredRead(reads, 'execution', spec.path, `config.sections.${key}`, spec.optional);
|
|
59
|
+
}
|
|
60
|
+
return reads;
|
|
61
|
+
}
|
|
62
|
+
if (cfg.strategy === 'question-driven') {
|
|
63
|
+
if (isPlainObject(cfg.meta)) {
|
|
64
|
+
for (const [key, binding] of Object.entries(cfg.meta)) {
|
|
65
|
+
if (!isPlainObject(binding))
|
|
66
|
+
continue;
|
|
67
|
+
if (binding.type === 'executionMemoryPath') {
|
|
68
|
+
pushRequiredRead(reads, 'execution', binding.path, `config.meta.${key}`, binding.optional);
|
|
69
|
+
}
|
|
70
|
+
else if (binding.type === 'outputsMemoryPath') {
|
|
71
|
+
pushRequiredRead(reads, 'outputs', binding.path, `config.meta.${key}`, binding.optional);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (isPlainObject(cfg.items)) {
|
|
76
|
+
for (const [key, spec] of Object.entries(cfg.items)) {
|
|
77
|
+
if (!isPlainObject(spec) || spec.optional === true)
|
|
78
|
+
continue;
|
|
79
|
+
if (typeof spec.answerPath === 'string' && spec.answerPath.length > 0) {
|
|
80
|
+
pushRequiredRead(reads, 'execution', spec.answerPath, `config.items.${key}.answerPath`, false);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const nodeId = typeof spec.nodeId === 'string' ? spec.nodeId : undefined;
|
|
84
|
+
const node = nodeId && graph ? getNodeById(graph, nodeId) : undefined;
|
|
85
|
+
const defaultAnswerPath = node?.executionMapping?.path;
|
|
86
|
+
pushRequiredRead(reads, 'execution', defaultAnswerPath, `config.items.${key}.node.executionMapping.path`, false);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return reads;
|
|
91
|
+
}
|
|
92
|
+
function reachableFrom(from, to, outgoingByFrom) {
|
|
93
|
+
if (from === to)
|
|
94
|
+
return true;
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
const q = [from];
|
|
97
|
+
while (q.length) {
|
|
98
|
+
const cur = q.shift();
|
|
99
|
+
if (cur === to)
|
|
100
|
+
return true;
|
|
101
|
+
if (seen.has(cur))
|
|
102
|
+
continue;
|
|
103
|
+
seen.add(cur);
|
|
104
|
+
for (const nxt of outgoingByFrom.get(cur) ?? []) {
|
|
105
|
+
if (!seen.has(nxt))
|
|
106
|
+
q.push(nxt);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
export function assertFinalizerRequiredReadsResolvable(args) {
|
|
112
|
+
const { graph, finalizer, executionMemory, outputsMemory } = args;
|
|
113
|
+
const finalizerNodeId = String(finalizer.id);
|
|
114
|
+
const reads = collectRequiredFinalizerExecutionMemoryReads(finalizer, graph);
|
|
115
|
+
if (reads.length === 0)
|
|
116
|
+
return;
|
|
117
|
+
const outgoingByFrom = new Map();
|
|
118
|
+
const edges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
119
|
+
for (const edge of edges) {
|
|
120
|
+
const from = edge?.from;
|
|
121
|
+
const to = edge?.to;
|
|
122
|
+
if (typeof from !== 'string' || typeof to !== 'string')
|
|
123
|
+
continue;
|
|
124
|
+
const list = outgoingByFrom.get(from) ?? [];
|
|
125
|
+
list.push(to);
|
|
126
|
+
outgoingByFrom.set(from, list);
|
|
127
|
+
}
|
|
128
|
+
const writers = asArrayNodes(graph)
|
|
129
|
+
.filter((node) => node?.type !== 'finalizer')
|
|
130
|
+
.map((node) => ({
|
|
131
|
+
nodeId: String(node.id),
|
|
132
|
+
executionPath: node?.executionMapping?.path,
|
|
133
|
+
}))
|
|
134
|
+
.flatMap((writer) => {
|
|
135
|
+
const out = [];
|
|
136
|
+
if (typeof writer.executionPath === 'string' && writer.executionPath.length > 0) {
|
|
137
|
+
out.push({ nodeId: writer.nodeId, memory: 'execution', path: writer.executionPath });
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
});
|
|
141
|
+
for (const read of reads) {
|
|
142
|
+
const seededValue = getByDotPath(read.memory === 'outputs' ? outputsMemory : executionMemory, read.path);
|
|
143
|
+
if (seededValue !== undefined && seededValue !== null)
|
|
144
|
+
continue;
|
|
145
|
+
const matchingWriters = writers.filter((writer) => writer.memory === read.memory && memoryPathsMatch(read.path, writer.path));
|
|
146
|
+
if (matchingWriters.length === 0) {
|
|
147
|
+
const selectorType = read.memory === 'outputs' ? 'outputsMemoryPath' : 'executionMemoryPath';
|
|
148
|
+
const writerField = read.memory === 'outputs' ? 'runtime.outputsMemory seed' : 'executionMapping.path';
|
|
149
|
+
throw createFinalizerError({
|
|
150
|
+
code: 'GRAPH_FINALIZER_INPUT_MISSING',
|
|
151
|
+
message: `Finalizer required input has no ${read.memory} seed or upstream ${writerField} writer (${selectorType}="${read.path}")`,
|
|
152
|
+
finalizerNodeId,
|
|
153
|
+
path: read.sources[0],
|
|
154
|
+
details: {
|
|
155
|
+
reason: 'missing-writer',
|
|
156
|
+
read,
|
|
157
|
+
graphId: graph.id,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const reachableWriters = matchingWriters.filter((writer) => reachableFrom(writer.nodeId, finalizerNodeId, outgoingByFrom));
|
|
162
|
+
if (reachableWriters.length === 0) {
|
|
163
|
+
const selectorType = read.memory === 'outputs' ? 'outputsMemoryPath' : 'executionMemoryPath';
|
|
164
|
+
throw createFinalizerError({
|
|
165
|
+
code: 'GRAPH_FINALIZER_INPUT_MISSING',
|
|
166
|
+
message: `Finalizer required input writer is not ordered before finalizer (${selectorType}="${read.path}")`,
|
|
167
|
+
finalizerNodeId,
|
|
168
|
+
path: read.sources[0],
|
|
169
|
+
details: {
|
|
170
|
+
reason: 'missing-dependency-edge',
|
|
171
|
+
read,
|
|
172
|
+
matchingWriters,
|
|
173
|
+
expectedReachability: matchingWriters.map((writer) => ({
|
|
174
|
+
from: writer.nodeId,
|
|
175
|
+
to: finalizerNodeId,
|
|
176
|
+
})),
|
|
177
|
+
graphId: graph.id,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function validateBinding(name, b, finalizerNodeId) {
|
|
184
|
+
if (!isPlainObject(b)) {
|
|
185
|
+
throw createFinalizerError({
|
|
186
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
187
|
+
message: `Finalizer input "${name}" must be an object binding`,
|
|
188
|
+
finalizerNodeId,
|
|
189
|
+
path: `inputs.${name}`,
|
|
190
|
+
details: { binding: b },
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const type = b.type;
|
|
194
|
+
if (type === 'executionMemoryPath' || type === 'outputsMemoryPath') {
|
|
195
|
+
const path = b.path;
|
|
196
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
197
|
+
throw createFinalizerError({
|
|
198
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
199
|
+
message: `Finalizer input "${name}" ${type} requires a non-empty path`,
|
|
200
|
+
finalizerNodeId,
|
|
201
|
+
path: `inputs.${name}.path`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const optional = b.optional;
|
|
205
|
+
if (optional !== undefined && typeof optional !== 'boolean') {
|
|
206
|
+
throw createFinalizerError({
|
|
207
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
208
|
+
message: `Finalizer input "${name}" optional must be boolean`,
|
|
209
|
+
finalizerNodeId,
|
|
210
|
+
path: `inputs.${name}.optional`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (type === 'literal')
|
|
216
|
+
return;
|
|
217
|
+
throw createFinalizerError({
|
|
218
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
219
|
+
message: `Finalizer input "${name}" has unsupported binding type "${String(type)}"`,
|
|
220
|
+
finalizerNodeId,
|
|
221
|
+
path: `inputs.${name}.type`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function validateAggregateConfig(cfg, finalizerNodeId) {
|
|
225
|
+
if (!isPlainObject(cfg) || typeof cfg.strategy !== 'string') {
|
|
226
|
+
throw createFinalizerError({
|
|
227
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
228
|
+
message: `Aggregate finalizer requires config.strategy`,
|
|
229
|
+
finalizerNodeId,
|
|
230
|
+
path: 'config.strategy',
|
|
231
|
+
details: { config: cfg },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
const strategy = cfg.strategy;
|
|
235
|
+
if (strategy === 'object-map') {
|
|
236
|
+
if (!isPlainObject(cfg.map)) {
|
|
237
|
+
throw createFinalizerError({
|
|
238
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
239
|
+
message: `Aggregate finalizer requires config.map`,
|
|
240
|
+
finalizerNodeId,
|
|
241
|
+
path: 'config.map',
|
|
242
|
+
details: { config: cfg },
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (strategy === 'report-schema') {
|
|
248
|
+
const sections = cfg.sections;
|
|
249
|
+
if (sections !== undefined && !isPlainObject(sections)) {
|
|
250
|
+
throw createFinalizerError({
|
|
251
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
252
|
+
message: `report-schema aggregate finalizer config.sections must be an object when provided`,
|
|
253
|
+
finalizerNodeId,
|
|
254
|
+
path: 'config.sections',
|
|
255
|
+
details: { config: cfg },
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (isPlainObject(sections)) {
|
|
259
|
+
for (const [k, spec] of Object.entries(sections)) {
|
|
260
|
+
if (!isPlainObject(spec) || typeof spec.path !== 'string' || spec.path.length === 0) {
|
|
261
|
+
throw createFinalizerError({
|
|
262
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
263
|
+
message: `report-schema section "${k}" must have a non-empty path`,
|
|
264
|
+
finalizerNodeId,
|
|
265
|
+
path: `config.sections.${k}.path`,
|
|
266
|
+
details: { spec },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const optional = spec.optional;
|
|
270
|
+
if (optional !== undefined && typeof optional !== 'boolean') {
|
|
271
|
+
throw createFinalizerError({
|
|
272
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
273
|
+
message: `report-schema section "${k}" optional must be boolean when provided`,
|
|
274
|
+
finalizerNodeId,
|
|
275
|
+
path: `config.sections.${k}.optional`,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const collectTags = cfg.collect_tags;
|
|
281
|
+
if (collectTags !== undefined && typeof collectTags !== 'boolean') {
|
|
282
|
+
throw createFinalizerError({
|
|
283
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
284
|
+
message: `report-schema aggregate finalizer config.collect_tags must be boolean when provided`,
|
|
285
|
+
finalizerNodeId,
|
|
286
|
+
path: 'config.collect_tags',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const meta = cfg.meta;
|
|
290
|
+
if (meta !== undefined && !isPlainObject(meta)) {
|
|
291
|
+
throw createFinalizerError({
|
|
292
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
293
|
+
message: `report-schema aggregate finalizer config.meta must be an object when provided`,
|
|
294
|
+
finalizerNodeId,
|
|
295
|
+
path: 'config.meta',
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (strategy === 'question-driven') {
|
|
301
|
+
if (!isPlainObject(cfg.items)) {
|
|
302
|
+
throw createFinalizerError({
|
|
303
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
304
|
+
message: `question-driven aggregate finalizer requires config.items`,
|
|
305
|
+
finalizerNodeId,
|
|
306
|
+
path: 'config.items',
|
|
307
|
+
details: { config: cfg },
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
for (const [k, spec] of Object.entries(cfg.items)) {
|
|
311
|
+
if (!isPlainObject(spec) || typeof spec.nodeId !== 'string' || spec.nodeId.length === 0) {
|
|
312
|
+
throw createFinalizerError({
|
|
313
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
314
|
+
message: `question-driven item "${k}" must specify a non-empty nodeId`,
|
|
315
|
+
finalizerNodeId,
|
|
316
|
+
path: `config.items.${k}.nodeId`,
|
|
317
|
+
details: { item: spec },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const qp = spec.questionPath;
|
|
321
|
+
if (qp !== undefined && (typeof qp !== 'string' || qp.length === 0)) {
|
|
322
|
+
throw createFinalizerError({
|
|
323
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
324
|
+
message: `question-driven item "${k}" questionPath must be a non-empty string when provided`,
|
|
325
|
+
finalizerNodeId,
|
|
326
|
+
path: `config.items.${k}.questionPath`,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
const ap = spec.answerPath;
|
|
330
|
+
if (ap !== undefined && (typeof ap !== 'string' || ap.length === 0)) {
|
|
331
|
+
throw createFinalizerError({
|
|
332
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
333
|
+
message: `question-driven item "${k}" answerPath must be a non-empty string when provided`,
|
|
334
|
+
finalizerNodeId,
|
|
335
|
+
path: `config.items.${k}.answerPath`,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (cfg.meta !== undefined && !isPlainObject(cfg.meta)) {
|
|
340
|
+
throw createFinalizerError({
|
|
341
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
342
|
+
message: `question-driven aggregate finalizer meta must be an object when provided`,
|
|
343
|
+
finalizerNodeId,
|
|
344
|
+
path: 'config.meta',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
throw createFinalizerError({
|
|
350
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
351
|
+
message: `Unsupported aggregate config.strategy "${String(strategy)}"`,
|
|
352
|
+
finalizerNodeId,
|
|
353
|
+
path: 'config.strategy',
|
|
354
|
+
details: { config: cfg },
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function validateBundleConfig(cfg, finalizerNodeId) {
|
|
358
|
+
if (!isPlainObject(cfg) || cfg.strategy !== 'bundle' || !Array.isArray(cfg.includeInputs)) {
|
|
359
|
+
throw createFinalizerError({
|
|
360
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
361
|
+
message: `Bundle finalizer requires config.strategy="bundle" and includeInputs[]`,
|
|
362
|
+
finalizerNodeId,
|
|
363
|
+
path: 'config',
|
|
364
|
+
details: { config: cfg },
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function validateSelectConfig(cfg, finalizerNodeId) {
|
|
369
|
+
if (!isPlainObject(cfg) || cfg.strategy !== 'select' || !isPlainObject(cfg.selector)) {
|
|
370
|
+
throw createFinalizerError({
|
|
371
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
372
|
+
message: `Select finalizer requires config.strategy="select" and selector`,
|
|
373
|
+
finalizerNodeId,
|
|
374
|
+
path: 'config',
|
|
375
|
+
details: { config: cfg },
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function validateSynthesizeConfig(cfg, finalizerNodeId) {
|
|
380
|
+
if (!isPlainObject(cfg) || cfg.strategy !== 'synthesize') {
|
|
381
|
+
throw createFinalizerError({
|
|
382
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
383
|
+
message: `Synthesize finalizer requires config.strategy="synthesize"`,
|
|
384
|
+
finalizerNodeId,
|
|
385
|
+
path: 'config',
|
|
386
|
+
details: { config: cfg },
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
const utilityKey = cfg.utilityKey;
|
|
390
|
+
const templateId = cfg.templateId;
|
|
391
|
+
if (typeof utilityKey !== 'string' || utilityKey.length === 0) {
|
|
392
|
+
throw createFinalizerError({
|
|
393
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
394
|
+
message: `Synthesize finalizer requires a non-empty config.utilityKey`,
|
|
395
|
+
finalizerNodeId,
|
|
396
|
+
path: 'config.utilityKey',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (typeof templateId !== 'string' || templateId.length === 0) {
|
|
400
|
+
throw createFinalizerError({
|
|
401
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
402
|
+
message: `Synthesize finalizer requires a non-empty config.templateId`,
|
|
403
|
+
finalizerNodeId,
|
|
404
|
+
path: 'config.templateId',
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
export function validateGraphFinalizer(graph) {
|
|
409
|
+
const nodes = asArrayNodes(graph);
|
|
410
|
+
const finalizers = nodes.filter((n) => n?.type === 'finalizer');
|
|
411
|
+
if (finalizers.length !== 1) {
|
|
412
|
+
throw createFinalizerError({
|
|
413
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
414
|
+
message: `Executable graph must have exactly one finalizer node (found ${finalizers.length})`,
|
|
415
|
+
finalizerNodeId: finalizers[0]?.id ?? '(none)',
|
|
416
|
+
details: { graphId: graph.id, finalizerCount: finalizers.length },
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
const finalizer = finalizers[0];
|
|
420
|
+
const finalizerNodeId = String(finalizer.id);
|
|
421
|
+
const ft = finalizer.finalizerType;
|
|
422
|
+
if (typeof ft !== 'string' || ft.length === 0) {
|
|
423
|
+
throw createFinalizerError({
|
|
424
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
425
|
+
message: 'Finalizer must specify finalizerType',
|
|
426
|
+
finalizerNodeId,
|
|
427
|
+
path: 'finalizerType',
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
const edges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
431
|
+
const out = edges.filter((e) => e && typeof e === 'object' && e.from === finalizerNodeId);
|
|
432
|
+
if (out.length > 0) {
|
|
433
|
+
throw createFinalizerError({
|
|
434
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
435
|
+
message: `Finalizer node "${finalizerNodeId}" must have no outgoing edges`,
|
|
436
|
+
finalizerNodeId,
|
|
437
|
+
details: { outgoingEdges: out.length },
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// Reachability (conservative: treat all edges as potentially active).
|
|
441
|
+
const incomingByTo = new Map();
|
|
442
|
+
const outgoingByFrom = new Map();
|
|
443
|
+
for (const e of edges) {
|
|
444
|
+
const from = e?.from;
|
|
445
|
+
const to = e?.to;
|
|
446
|
+
if (typeof from !== 'string' || typeof to !== 'string')
|
|
447
|
+
continue;
|
|
448
|
+
(incomingByTo.get(to) ?? incomingByTo.set(to, []).get(to)).push(from);
|
|
449
|
+
(outgoingByFrom.get(from) ?? outgoingByFrom.set(from, []).get(from)).push(to);
|
|
450
|
+
}
|
|
451
|
+
const nodeIds = new Set(nodes.map((n) => String(n.id)));
|
|
452
|
+
const roots = Array.from(nodeIds).filter((id) => (incomingByTo.get(id) ?? []).length === 0);
|
|
453
|
+
const seen = new Set();
|
|
454
|
+
const q = [...roots];
|
|
455
|
+
while (q.length) {
|
|
456
|
+
const cur = q.shift();
|
|
457
|
+
if (seen.has(cur))
|
|
458
|
+
continue;
|
|
459
|
+
seen.add(cur);
|
|
460
|
+
for (const nxt of outgoingByFrom.get(cur) ?? [])
|
|
461
|
+
if (!seen.has(nxt))
|
|
462
|
+
q.push(nxt);
|
|
463
|
+
}
|
|
464
|
+
if (!seen.has(finalizerNodeId)) {
|
|
465
|
+
throw createFinalizerError({
|
|
466
|
+
code: 'GRAPH_FINALIZER_UNREACHABLE',
|
|
467
|
+
message: `Finalizer node "${finalizerNodeId}" must be reachable from at least one start path`,
|
|
468
|
+
finalizerNodeId,
|
|
469
|
+
details: { roots },
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
if (!isPlainObject(finalizer.inputs)) {
|
|
473
|
+
throw createFinalizerError({
|
|
474
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
475
|
+
message: 'Finalizer must specify inputs as an object',
|
|
476
|
+
finalizerNodeId,
|
|
477
|
+
path: 'inputs',
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
for (const [name, b] of Object.entries(finalizer.inputs)) {
|
|
481
|
+
validateBinding(name, b, finalizerNodeId);
|
|
482
|
+
}
|
|
483
|
+
if (finalizer.config == null) {
|
|
484
|
+
throw createFinalizerError({
|
|
485
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
486
|
+
message: 'Finalizer must specify config',
|
|
487
|
+
finalizerNodeId,
|
|
488
|
+
path: 'config',
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
// Validate outputSchema shape (type + basic fields)
|
|
492
|
+
if (finalizer.outputSchema != null) {
|
|
493
|
+
const shape = validateOutputSchemaShape(finalizer.outputSchema);
|
|
494
|
+
if (!shape.ok) {
|
|
495
|
+
throw createFinalizerError({
|
|
496
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
497
|
+
message: `Invalid outputSchema: ${shape.message}`,
|
|
498
|
+
finalizerNodeId,
|
|
499
|
+
path: 'outputSchema',
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Config shape by finalizerType (implemented now)
|
|
504
|
+
const cfg = finalizer.config;
|
|
505
|
+
switch (ft) {
|
|
506
|
+
case 'aggregate':
|
|
507
|
+
validateAggregateConfig(cfg, finalizerNodeId);
|
|
508
|
+
break;
|
|
509
|
+
case 'bundle':
|
|
510
|
+
validateBundleConfig(cfg, finalizerNodeId);
|
|
511
|
+
break;
|
|
512
|
+
case 'select':
|
|
513
|
+
validateSelectConfig(cfg, finalizerNodeId);
|
|
514
|
+
break;
|
|
515
|
+
case 'synthesize':
|
|
516
|
+
validateSynthesizeConfig(cfg, finalizerNodeId);
|
|
517
|
+
break;
|
|
518
|
+
case 'compose':
|
|
519
|
+
throw createFinalizerError({
|
|
520
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
521
|
+
message: `Finalizer type "compose" is not implemented in this runtime yet`,
|
|
522
|
+
finalizerNodeId,
|
|
523
|
+
path: 'finalizerType',
|
|
524
|
+
});
|
|
525
|
+
default:
|
|
526
|
+
throw createFinalizerError({
|
|
527
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
528
|
+
message: `Unsupported finalizerType "${String(ft)}"`,
|
|
529
|
+
finalizerNodeId,
|
|
530
|
+
path: 'finalizerType',
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return { finalizer };
|
|
534
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON.stringify with sorted object keys (recursive) so equivalent documents hash identically.
|
|
3
|
+
*/
|
|
4
|
+
export declare function stableStringifyGraphDocument(value: unknown): string;
|
|
5
|
+
/**
|
|
6
|
+
* SHA-256 (hex) of the stable JSON encoding of `graph`, for audit trails and matrix persistence.
|
|
7
|
+
*/
|
|
8
|
+
export declare function computeGraphDocumentContentSha256(graph: unknown): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* JSON.stringify with sorted object keys (recursive) so equivalent documents hash identically.
|
|
4
|
+
*/
|
|
5
|
+
export function stableStringifyGraphDocument(value) {
|
|
6
|
+
if (value === null || typeof value !== 'object') {
|
|
7
|
+
return JSON.stringify(value);
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
return `[${value.map((v) => stableStringifyGraphDocument(v)).join(',')}]`;
|
|
11
|
+
}
|
|
12
|
+
const obj = value;
|
|
13
|
+
const keys = Object.keys(obj).sort();
|
|
14
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringifyGraphDocument(obj[k])}`).join(',')}}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* SHA-256 (hex) of the stable JSON encoding of `graph`, for audit trails and matrix persistence.
|
|
18
|
+
*/
|
|
19
|
+
export function computeGraphDocumentContentSha256(graph) {
|
|
20
|
+
return createHash('sha256').update(stableStringifyGraphDocument(graph), 'utf8').digest('hex');
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowed memory-path roots for smartInput.paths and taskConfiguration.aiTaskProfile.inputSynthesis.sources.
|
|
3
|
+
* `input` and `inputs` are distinct roots (see `.docs/CR-input-and-inputs-dual-root.md`).
|
|
4
|
+
*/
|
|
5
|
+
export declare const GRAPH_ENGINE_MEMORY_PATH_ROOTS: readonly ["input", "inputs", "variables", "jobVariables", "taskVariables", "jobMemory", "taskMemory", "executionMemory", "xynthesized.job", "xynthesized.task", "xynthesized.execution"];
|
|
6
|
+
/** Splits a path into allowlisted root + dot tail (longest root wins, e.g. `inputs` before `input`). */
|
|
7
|
+
export declare function splitGraphEngineMemoryPath(path: string): {
|
|
8
|
+
root: string;
|
|
9
|
+
tail: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function isAllowedGraphEngineMemoryPath(path: string): boolean;
|
|
12
|
+
export declare function graphEngineMemoryPathValidationMessage(path: string): string;
|