@x12i/graphenix-executable-format 1.0.0 → 2.0.1
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/README.md +512 -341
- package/dist/api.d.ts +13 -35
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +13 -98
- package/dist/api.js.map +1 -1
- package/dist/constants.d.ts +1 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -18
- package/dist/constants.js.map +1 -1
- package/dist/index.js +1 -17
- package/dist/index.js.map +1 -1
- package/dist/test/executable-format.test.js +316 -289
- package/dist/test/executable-format.test.js.map +1 -1
- package/dist/test/executable-plan-v2.test.d.ts +2 -0
- package/dist/test/executable-plan-v2.test.d.ts.map +1 -0
- package/dist/test/executable-plan-v2.test.js +342 -0
- package/dist/test/executable-plan-v2.test.js.map +1 -0
- package/dist/test/execution-trace-v2.test.d.ts +2 -0
- package/dist/test/execution-trace-v2.test.d.ts.map +1 -0
- package/dist/test/execution-trace-v2.test.js +411 -0
- package/dist/test/execution-trace-v2.test.js.map +1 -0
- package/dist/test/phase-1-format.test.d.ts +2 -0
- package/dist/test/phase-1-format.test.d.ts.map +1 -0
- package/dist/test/phase-1-format.test.js +297 -0
- package/dist/test/phase-1-format.test.js.map +1 -0
- package/package.json +69 -56
- package/dist/adapter/build-graph-execution-request-from-studio-execute.d.ts +0 -6
- package/dist/adapter/build-graph-execution-request-from-studio-execute.d.ts.map +0 -1
- package/dist/adapter/build-graph-execution-request-from-studio-execute.js +0 -41
- package/dist/adapter/build-graph-execution-request-from-studio-execute.js.map +0 -1
- package/dist/case-condition/allowed-paths.d.ts +0 -4
- package/dist/case-condition/allowed-paths.d.ts.map +0 -1
- package/dist/case-condition/allowed-paths.js +0 -74
- package/dist/case-condition/allowed-paths.js.map +0 -1
- package/dist/case-condition/evaluate-case-condition.d.ts +0 -4
- package/dist/case-condition/evaluate-case-condition.d.ts.map +0 -1
- package/dist/case-condition/evaluate-case-condition.js +0 -106
- package/dist/case-condition/evaluate-case-condition.js.map +0 -1
- package/dist/case-condition/select-graph-model-case.d.ts +0 -6
- package/dist/case-condition/select-graph-model-case.d.ts.map +0 -1
- package/dist/case-condition/select-graph-model-case.js +0 -40
- package/dist/case-condition/select-graph-model-case.js.map +0 -1
- package/dist/case-condition/select-node-model-case.d.ts +0 -4
- package/dist/case-condition/select-node-model-case.d.ts.map +0 -1
- package/dist/case-condition/select-node-model-case.js +0 -34
- package/dist/case-condition/select-node-model-case.js.map +0 -1
- package/dist/case-condition/validate-case-condition.d.ts +0 -5
- package/dist/case-condition/validate-case-condition.d.ts.map +0 -1
- package/dist/case-condition/validate-case-condition.js +0 -118
- package/dist/case-condition/validate-case-condition.js.map +0 -1
- package/dist/compile/build-node-execution-units.d.ts +0 -6
- package/dist/compile/build-node-execution-units.d.ts.map +0 -1
- package/dist/compile/build-node-execution-units.js +0 -77
- package/dist/compile/build-node-execution-units.js.map +0 -1
- package/dist/compile/compile-executable-plan.d.ts +0 -5
- package/dist/compile/compile-executable-plan.d.ts.map +0 -1
- package/dist/compile/compile-executable-plan.js +0 -142
- package/dist/compile/compile-executable-plan.js.map +0 -1
- package/dist/explain/explain-executable-graph-error.d.ts +0 -4
- package/dist/explain/explain-executable-graph-error.d.ts.map +0 -1
- package/dist/explain/explain-executable-graph-error.js +0 -14
- package/dist/explain/explain-executable-graph-error.js.map +0 -1
- package/dist/explain/explain-node-inheritance.d.ts +0 -3
- package/dist/explain/explain-node-inheritance.d.ts.map +0 -1
- package/dist/explain/explain-node-inheritance.js +0 -41
- package/dist/explain/explain-node-inheritance.js.map +0 -1
- package/dist/fixtures/minimal-executable-graph.d.ts +0 -4
- package/dist/fixtures/minimal-executable-graph.d.ts.map +0 -1
- package/dist/fixtures/minimal-executable-graph.js +0 -170
- package/dist/fixtures/minimal-executable-graph.js.map +0 -1
- package/dist/helpers/add-finalizer-node.d.ts +0 -5
- package/dist/helpers/add-finalizer-node.d.ts.map +0 -1
- package/dist/helpers/add-finalizer-node.js +0 -13
- package/dist/helpers/add-finalizer-node.js.map +0 -1
- package/dist/helpers/add-task-node.d.ts +0 -5
- package/dist/helpers/add-task-node.d.ts.map +0 -1
- package/dist/helpers/add-task-node.js +0 -13
- package/dist/helpers/add-task-node.js.map +0 -1
- package/dist/helpers/connect-task-to-task.d.ts +0 -10
- package/dist/helpers/connect-task-to-task.d.ts.map +0 -1
- package/dist/helpers/connect-task-to-task.js +0 -23
- package/dist/helpers/connect-task-to-task.js.map +0 -1
- package/dist/helpers/set-graph-model-config.d.ts +0 -5
- package/dist/helpers/set-graph-model-config.d.ts.map +0 -1
- package/dist/helpers/set-graph-model-config.js +0 -30
- package/dist/helpers/set-graph-model-config.js.map +0 -1
- package/dist/helpers/set-node-model-config.d.ts +0 -5
- package/dist/helpers/set-node-model-config.d.ts.map +0 -1
- package/dist/helpers/set-node-model-config.js +0 -25
- package/dist/helpers/set-node-model-config.js.map +0 -1
- package/dist/internal/hash.d.ts +0 -4
- package/dist/internal/hash.d.ts.map +0 -1
- package/dist/internal/hash.js +0 -26
- package/dist/internal/hash.js.map +0 -1
- package/dist/internal/utils.d.ts +0 -16
- package/dist/internal/utils.d.ts.map +0 -1
- package/dist/internal/utils.js +0 -80
- package/dist/internal/utils.js.map +0 -1
- package/dist/migration/migrate-legacy-graph-model-object.d.ts +0 -4
- package/dist/migration/migrate-legacy-graph-model-object.d.ts.map +0 -1
- package/dist/migration/migrate-legacy-graph-model-object.js +0 -217
- package/dist/migration/migrate-legacy-graph-model-object.js.map +0 -1
- package/dist/normalize/normalize-executable-graph.d.ts +0 -3
- package/dist/normalize/normalize-executable-graph.d.ts.map +0 -1
- package/dist/normalize/normalize-executable-graph.js +0 -34
- package/dist/normalize/normalize-executable-graph.js.map +0 -1
- package/dist/normalize/normalize-model-config.d.ts +0 -4
- package/dist/normalize/normalize-model-config.d.ts.map +0 -1
- package/dist/normalize/normalize-model-config.js +0 -64
- package/dist/normalize/normalize-model-config.js.map +0 -1
- package/dist/profile-choice/known-profile-choices.d.ts +0 -10
- package/dist/profile-choice/known-profile-choices.d.ts.map +0 -1
- package/dist/profile-choice/known-profile-choices.js +0 -19
- package/dist/profile-choice/known-profile-choices.js.map +0 -1
- package/dist/profile-choice/validate-profile-choice.d.ts +0 -8
- package/dist/profile-choice/validate-profile-choice.d.ts.map +0 -1
- package/dist/profile-choice/validate-profile-choice.js +0 -55
- package/dist/profile-choice/validate-profile-choice.js.map +0 -1
- package/dist/resolution/build-deterministic-case-context.d.ts +0 -9
- package/dist/resolution/build-deterministic-case-context.d.ts.map +0 -1
- package/dist/resolution/build-deterministic-case-context.js +0 -26
- package/dist/resolution/build-deterministic-case-context.js.map +0 -1
- package/dist/resolution/resolve-graph-model-config.d.ts +0 -12
- package/dist/resolution/resolve-graph-model-config.d.ts.map +0 -1
- package/dist/resolution/resolve-graph-model-config.js +0 -28
- package/dist/resolution/resolve-graph-model-config.js.map +0 -1
- package/dist/resolution/resolve-node-ai-plan.d.ts +0 -7
- package/dist/resolution/resolve-node-ai-plan.d.ts.map +0 -1
- package/dist/resolution/resolve-node-ai-plan.js +0 -64
- package/dist/resolution/resolve-node-ai-plan.js.map +0 -1
- package/dist/resolution/resolve-node-model-config.d.ts +0 -15
- package/dist/resolution/resolve-node-model-config.d.ts.map +0 -1
- package/dist/resolution/resolve-node-model-config.js +0 -37
- package/dist/resolution/resolve-node-model-config.js.map +0 -1
- package/dist/resolution/resolve-node-model-slot.d.ts +0 -7
- package/dist/resolution/resolve-node-model-slot.d.ts.map +0 -1
- package/dist/resolution/resolve-node-model-slot.js +0 -65
- package/dist/resolution/resolve-node-model-slot.js.map +0 -1
- package/dist/trace/append-execution-event.d.ts +0 -5
- package/dist/trace/append-execution-event.d.ts.map +0 -1
- package/dist/trace/append-execution-event.js +0 -9
- package/dist/trace/append-execution-event.js.map +0 -1
- package/dist/trace/create-execution-trace.d.ts +0 -5
- package/dist/trace/create-execution-trace.d.ts.map +0 -1
- package/dist/trace/create-execution-trace.js +0 -56
- package/dist/trace/create-execution-trace.js.map +0 -1
- package/dist/types/case-condition.d.ts +0 -50
- package/dist/types/case-condition.d.ts.map +0 -1
- package/dist/types/case-condition.js +0 -3
- package/dist/types/case-condition.js.map +0 -1
- package/dist/types/errors.d.ts +0 -16
- package/dist/types/errors.d.ts.map +0 -1
- package/dist/types/errors.js +0 -3
- package/dist/types/errors.js.map +0 -1
- package/dist/types/executable-graph.d.ts +0 -19
- package/dist/types/executable-graph.d.ts.map +0 -1
- package/dist/types/executable-graph.js +0 -3
- package/dist/types/executable-graph.js.map +0 -1
- package/dist/types/executable-nodes.d.ts +0 -54
- package/dist/types/executable-nodes.d.ts.map +0 -1
- package/dist/types/executable-nodes.js +0 -3
- package/dist/types/executable-nodes.js.map +0 -1
- package/dist/types/executable-plan.d.ts +0 -78
- package/dist/types/executable-plan.d.ts.map +0 -1
- package/dist/types/executable-plan.js +0 -5
- package/dist/types/executable-plan.js.map +0 -1
- package/dist/types/executable-profile.d.ts +0 -18
- package/dist/types/executable-profile.d.ts.map +0 -1
- package/dist/types/executable-profile.js +0 -3
- package/dist/types/executable-profile.js.map +0 -1
- package/dist/types/execution-trace.d.ts +0 -278
- package/dist/types/execution-trace.d.ts.map +0 -1
- package/dist/types/execution-trace.js +0 -5
- package/dist/types/execution-trace.js.map +0 -1
- package/dist/types/execution-unit.d.ts +0 -19
- package/dist/types/execution-unit.d.ts.map +0 -1
- package/dist/types/execution-unit.js +0 -3
- package/dist/types/execution-unit.js.map +0 -1
- package/dist/types/index.d.ts +0 -16
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -8
- package/dist/types/index.js.map +0 -1
- package/dist/types/legacy.d.ts +0 -21
- package/dist/types/legacy.d.ts.map +0 -1
- package/dist/types/legacy.js +0 -3
- package/dist/types/legacy.js.map +0 -1
- package/dist/types/model-config.d.ts +0 -80
- package/dist/types/model-config.d.ts.map +0 -1
- package/dist/types/model-config.js +0 -3
- package/dist/types/model-config.js.map +0 -1
- package/dist/types/resolution.d.ts +0 -55
- package/dist/types/resolution.d.ts.map +0 -1
- package/dist/types/resolution.js +0 -3
- package/dist/types/resolution.js.map +0 -1
- package/dist/types/runtime.d.ts +0 -19
- package/dist/types/runtime.d.ts.map +0 -1
- package/dist/types/runtime.js +0 -3
- package/dist/types/runtime.js.map +0 -1
- package/dist/types/studio-execute.d.ts +0 -25
- package/dist/types/studio-execute.d.ts.map +0 -1
- package/dist/types/studio-execute.js +0 -3
- package/dist/types/studio-execute.js.map +0 -1
- package/dist/validators/assert-executable-graph.d.ts +0 -3
- package/dist/validators/assert-executable-graph.d.ts.map +0 -1
- package/dist/validators/assert-executable-graph.js +0 -14
- package/dist/validators/assert-executable-graph.js.map +0 -1
- package/dist/validators/assert-normalized-executable-graph.d.ts +0 -3
- package/dist/validators/assert-normalized-executable-graph.d.ts.map +0 -1
- package/dist/validators/assert-normalized-executable-graph.js +0 -14
- package/dist/validators/assert-normalized-executable-graph.js.map +0 -1
- package/dist/validators/validate-authoring-executable-graph.d.ts +0 -3
- package/dist/validators/validate-authoring-executable-graph.d.ts.map +0 -1
- package/dist/validators/validate-authoring-executable-graph.js +0 -134
- package/dist/validators/validate-authoring-executable-graph.js.map +0 -1
- package/dist/validators/validate-authoring-graph.d.ts +0 -2
- package/dist/validators/validate-authoring-graph.d.ts.map +0 -1
- package/dist/validators/validate-authoring-graph.js +0 -6
- package/dist/validators/validate-authoring-graph.js.map +0 -1
- package/dist/validators/validate-executable-graph.d.ts +0 -4
- package/dist/validators/validate-executable-graph.d.ts.map +0 -1
- package/dist/validators/validate-executable-graph.js +0 -9
- package/dist/validators/validate-executable-graph.js.map +0 -1
- package/dist/validators/validate-executable-plan.d.ts +0 -3
- package/dist/validators/validate-executable-plan.d.ts.map +0 -1
- package/dist/validators/validate-executable-plan.js +0 -211
- package/dist/validators/validate-executable-plan.js.map +0 -1
- package/dist/validators/validate-execution-trace.d.ts +0 -4
- package/dist/validators/validate-execution-trace.d.ts.map +0 -1
- package/dist/validators/validate-execution-trace.js +0 -158
- package/dist/validators/validate-execution-trace.js.map +0 -1
- package/dist/validators/validate-finalizer-node.d.ts +0 -4
- package/dist/validators/validate-finalizer-node.d.ts.map +0 -1
- package/dist/validators/validate-finalizer-node.js +0 -36
- package/dist/validators/validate-finalizer-node.js.map +0 -1
- package/dist/validators/validate-model-config.d.ts +0 -7
- package/dist/validators/validate-model-config.d.ts.map +0 -1
- package/dist/validators/validate-model-config.js +0 -197
- package/dist/validators/validate-model-config.js.map +0 -1
- package/dist/validators/validate-normalized-executable-graph.d.ts +0 -3
- package/dist/validators/validate-normalized-executable-graph.d.ts.map +0 -1
- package/dist/validators/validate-normalized-executable-graph.js +0 -26
- package/dist/validators/validate-normalized-executable-graph.js.map +0 -1
- package/dist/validators/validate-runtime-object.d.ts +0 -10
- package/dist/validators/validate-runtime-object.d.ts.map +0 -1
- package/dist/validators/validate-runtime-object.js +0 -63
- package/dist/validators/validate-runtime-object.js.map +0 -1
- package/dist/validators/validate-studio-execute-request.d.ts +0 -5
- package/dist/validators/validate-studio-execute-request.d.ts.map +0 -1
- package/dist/validators/validate-studio-execute-request.js +0 -37
- package/dist/validators/validate-studio-execute-request.js.map +0 -1
- package/dist/validators/validate-task-actions.d.ts +0 -4
- package/dist/validators/validate-task-actions.d.ts.map +0 -1
- package/dist/validators/validate-task-actions.js +0 -93
- package/dist/validators/validate-task-actions.js.map +0 -1
- package/dist/validators/validate-task-node.d.ts +0 -6
- package/dist/validators/validate-task-node.d.ts.map +0 -1
- package/dist/validators/validate-task-node.js +0 -43
- package/dist/validators/validate-task-node.js.map +0 -1
- package/schema/graphenix-executable-format-1.0.0.schema.json +0 -40
|
@@ -1,62 +1,55 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
-
const node_test_1 = require("node:test");
|
|
8
|
-
const graphenix_core_1 = require("@x12i/graphenix-core");
|
|
9
|
-
const api_js_1 = require("../api.js");
|
|
10
|
-
const minimal_executable_graph_js_1 = require("../fixtures/minimal-executable-graph.js");
|
|
11
|
-
const graphenix_core_2 = require("@x12i/graphenix-core");
|
|
12
|
-
const constants_js_1 = require("../constants.js");
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { validateGraph } from "@x12i/graphenix-core";
|
|
4
|
+
import { assertExecutableGraph, validateExecutableGraph, validateStudioExecuteRequest, validateRuntimeObject, buildGraphExecutionRequestFromStudioExecute, resolveNodeAiPlan, explainNodeInheritance, resolveNodeModelSlot, normalizeExecutableGraph, validateCaseCondition, evaluateCaseCondition, isProfileChoiceKeyFormat, isKnownProfileChoice, buildDeterministicCaseContext, compileExecutablePlan, validateExecutablePlan, validateAuthoringGraph, createExecutionTrace, appendExecutionEvent, validateExecutionTrace, buildRuntimeObject, createMinimalExecutableGraph, createPlainGraphenixGraph, migrateLegacyGraphModelObjectToGraphenixExecutable, validateAuthoringExecutableGraph } from "../api.js";
|
|
5
|
+
import { EXECUTABLE_PROFILE_NAMESPACE } from "@x12i/graphenix-executable-contracts";
|
|
13
6
|
function setGraphModelConfig(doc, modelConfig) {
|
|
14
7
|
const next = structuredClone(doc);
|
|
15
|
-
const ext = next.graph.metadata.extensions[
|
|
8
|
+
const ext = next.graph.metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
|
|
16
9
|
ext.modelConfig = modelConfig;
|
|
17
10
|
return next;
|
|
18
11
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const doc =
|
|
22
|
-
const result =
|
|
23
|
-
|
|
12
|
+
describe("graphenix base validation", () => {
|
|
13
|
+
it("valid executable graph passes Graphenix validateGraph()", () => {
|
|
14
|
+
const doc = createMinimalExecutableGraph();
|
|
15
|
+
const result = validateGraph(doc);
|
|
16
|
+
assert.equal(result.valid, true);
|
|
24
17
|
});
|
|
25
18
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const doc =
|
|
29
|
-
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const doc =
|
|
33
|
-
const result =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const doc =
|
|
39
|
-
const ext = doc.graph.metadata.extensions[
|
|
19
|
+
describe("executable profile validation", () => {
|
|
20
|
+
it("valid executable graph passes assertExecutableGraph()", () => {
|
|
21
|
+
const doc = createMinimalExecutableGraph();
|
|
22
|
+
assert.doesNotThrow(() => assertExecutableGraph(doc));
|
|
23
|
+
});
|
|
24
|
+
it("plain Graphenix graph without executable profile fails", () => {
|
|
25
|
+
const doc = createPlainGraphenixGraph();
|
|
26
|
+
const result = validateExecutableGraph(doc);
|
|
27
|
+
assert.equal(result.valid, false);
|
|
28
|
+
assert.ok(result.errors.some((e) => e.code === "EXECUTABLE_PROFILE_MISSING"));
|
|
29
|
+
});
|
|
30
|
+
it("graph-level modelConfig is required", () => {
|
|
31
|
+
const doc = createMinimalExecutableGraph();
|
|
32
|
+
const ext = doc.graph.metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
|
|
40
33
|
delete ext.modelConfig;
|
|
41
|
-
const result =
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
const result = validateExecutableGraph(doc);
|
|
35
|
+
assert.equal(result.valid, false);
|
|
36
|
+
assert.ok(result.errors.some((e) => e.code === "GRAPH_MODEL_CONFIG_MISSING"));
|
|
44
37
|
});
|
|
45
38
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const doc =
|
|
49
|
-
const plan =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const doc =
|
|
39
|
+
describe("model inheritance", () => {
|
|
40
|
+
it("node with no modelConfig inherits all slots", () => {
|
|
41
|
+
const doc = createMinimalExecutableGraph();
|
|
42
|
+
const plan = resolveNodeAiPlan(doc, "node:professional-answer");
|
|
43
|
+
assert.equal(plan.slots.preActionModel.inherited, true);
|
|
44
|
+
assert.equal(plan.slots.skillModel.inherited, true);
|
|
45
|
+
assert.equal(plan.slots.postActionModel.inherited, true);
|
|
46
|
+
assert.match(explainNodeInheritance(plan), /graph case default/);
|
|
47
|
+
});
|
|
48
|
+
it("node may override only skillModel", () => {
|
|
49
|
+
const doc = createMinimalExecutableGraph();
|
|
57
50
|
const taskNode = doc.graph.nodes.find((n) => n.id === "node:professional-answer");
|
|
58
51
|
const params = taskNode.parameters;
|
|
59
|
-
|
|
52
|
+
assert.ok(params && params.nodeType === "task");
|
|
60
53
|
taskNode.parameters = {
|
|
61
54
|
...params,
|
|
62
55
|
taskConfiguration: {
|
|
@@ -67,14 +60,14 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
67
60
|
}
|
|
68
61
|
}
|
|
69
62
|
};
|
|
70
|
-
const plan =
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
const plan = resolveNodeAiPlan(doc, "node:professional-answer");
|
|
64
|
+
assert.equal(plan.slots.skillModel.inherited, false);
|
|
65
|
+
assert.equal(plan.slots.preActionModel.inherited, true);
|
|
73
66
|
});
|
|
74
67
|
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const resolved =
|
|
68
|
+
describe("model fallback", () => {
|
|
69
|
+
it("fallback uses graph same-slot only", () => {
|
|
70
|
+
const resolved = resolveNodeModelSlot({
|
|
78
71
|
slot: "skillModel",
|
|
79
72
|
graphSelection: { kind: "profileChoice", key: "vol/default" },
|
|
80
73
|
nodeSelection: {
|
|
@@ -95,33 +88,33 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
95
88
|
graphCaseId: "default",
|
|
96
89
|
nodeId: "node:test"
|
|
97
90
|
});
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
assert.equal(resolved.source, "fallbackToGraphDefault");
|
|
92
|
+
assert.equal(resolved.selected.kind === "profileChoice" ? resolved.selected.key : "", "vol/default");
|
|
100
93
|
});
|
|
101
|
-
|
|
102
|
-
const runtime =
|
|
94
|
+
it("fallback does not use runtime or request model defaults", () => {
|
|
95
|
+
const runtime = validateRuntimeObject({
|
|
103
96
|
jobId: "job-1",
|
|
104
97
|
job: { id: "job-1", jobId: "job-1" },
|
|
105
98
|
graphDefaultModel: { kind: "profileChoice", key: "cheap/default" }
|
|
106
99
|
});
|
|
107
|
-
|
|
100
|
+
assert.equal(runtime.valid, false);
|
|
108
101
|
});
|
|
109
102
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const doc =
|
|
113
|
-
const result =
|
|
103
|
+
describe("studio adapter", () => {
|
|
104
|
+
it("studio request with graphDefaultModel fails", () => {
|
|
105
|
+
const doc = createMinimalExecutableGraph();
|
|
106
|
+
const result = validateStudioExecuteRequest({
|
|
114
107
|
mode: "graph",
|
|
115
108
|
jobId: "job-1",
|
|
116
109
|
graph: doc,
|
|
117
110
|
graphDefaultModel: {}
|
|
118
111
|
});
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
assert.equal(result.valid, false);
|
|
113
|
+
assert.ok(result.errors.some((e) => e.code === "STUDIO_GRAPH_DEFAULT_MODEL_FORBIDDEN"));
|
|
121
114
|
});
|
|
122
|
-
|
|
123
|
-
const doc =
|
|
124
|
-
const ext = doc.graph.metadata.extensions[
|
|
115
|
+
it("studio adapter outputs compiled { plan, runtime }", () => {
|
|
116
|
+
const doc = createMinimalExecutableGraph();
|
|
117
|
+
const ext = doc.graph.metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
|
|
125
118
|
ext.modelConfig = {
|
|
126
119
|
version: "graph-model-config/v1",
|
|
127
120
|
default: {
|
|
@@ -139,87 +132,121 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
139
132
|
},
|
|
140
133
|
openrouterApiKey: "secret-should-not-copy"
|
|
141
134
|
};
|
|
142
|
-
const execution =
|
|
135
|
+
const execution = buildGraphExecutionRequestFromStudioExecute(request, {
|
|
143
136
|
agentId: "agent-1"
|
|
144
137
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const profile = execution.plan.graph.graph.metadata?.extensions?.[
|
|
151
|
-
|
|
152
|
-
|
|
138
|
+
assert.equal(execution.runtime.jobId, "job-123");
|
|
139
|
+
assert.equal(execution.plan.source.graphId, doc.id);
|
|
140
|
+
assert.equal(execution.plan.format, "graphenix.executable-plan/v1");
|
|
141
|
+
assert.equal("openrouterApiKey" in execution.runtime, false);
|
|
142
|
+
assert.equal("openrouterApiKey" in execution.plan, false);
|
|
143
|
+
const profile = execution.plan.graph.graph.metadata?.extensions?.[EXECUTABLE_PROFILE_NAMESPACE];
|
|
144
|
+
assert.equal(profile?.modelConfig?.cases?.[0]?.id, "default");
|
|
145
|
+
assert.equal(validateExecutablePlan(execution.plan).valid, true);
|
|
153
146
|
});
|
|
154
147
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const result =
|
|
148
|
+
describe("runtime validation", () => {
|
|
149
|
+
it("credentials are never copied into runtime", () => {
|
|
150
|
+
const result = validateRuntimeObject({
|
|
158
151
|
jobId: "job-1",
|
|
159
152
|
job: { id: "job-1", jobId: "job-1" },
|
|
160
153
|
credentials: { token: "x" }
|
|
161
154
|
});
|
|
162
|
-
|
|
155
|
+
assert.equal(result.valid, false);
|
|
163
156
|
});
|
|
164
|
-
|
|
165
|
-
const result =
|
|
157
|
+
it("runtime modelConfig is rejected", () => {
|
|
158
|
+
const result = validateRuntimeObject({
|
|
166
159
|
jobId: "job-1",
|
|
167
160
|
job: { id: "job-1", jobId: "job-1" },
|
|
168
161
|
modelConfig: {}
|
|
169
162
|
});
|
|
170
|
-
|
|
163
|
+
assert.equal(result.valid, false);
|
|
171
164
|
});
|
|
172
165
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const migrated =
|
|
166
|
+
describe("legacy migration CRS-001", () => {
|
|
167
|
+
it("flat exellix graph migrates into valid authoring document", () => {
|
|
168
|
+
const migrated = migrateLegacyGraphModelObjectToGraphenixExecutable({
|
|
176
169
|
id: "graph:legacy",
|
|
177
170
|
version: "0.1.0",
|
|
178
171
|
nodes: [
|
|
179
172
|
{
|
|
180
173
|
id: "node:a",
|
|
181
|
-
|
|
182
|
-
skillKey: "
|
|
174
|
+
type: "task",
|
|
175
|
+
skillKey: "professional-answer",
|
|
176
|
+
conditions: {
|
|
177
|
+
jsonConditions: { path: "runtime.input.ready", op: "eq", value: true }
|
|
178
|
+
},
|
|
179
|
+
smartInput: { paths: ["runtime.input.question"] },
|
|
180
|
+
taskKnowledge: ["kb:faq"]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: "node:finalizer",
|
|
184
|
+
type: "finalizer",
|
|
185
|
+
finalizerType: "single-output",
|
|
186
|
+
config: { from: "node:a" }
|
|
183
187
|
}
|
|
184
188
|
],
|
|
185
|
-
edges: [],
|
|
189
|
+
edges: [{ from: "node:a", to: "node:finalizer" }],
|
|
186
190
|
modelConfig: {
|
|
187
191
|
version: "graph-model-config/v1",
|
|
188
192
|
cases: [
|
|
189
193
|
{
|
|
190
|
-
id: "default",
|
|
191
194
|
modelConfig: {
|
|
192
|
-
preActionModel:
|
|
193
|
-
skillModel:
|
|
194
|
-
postActionModel:
|
|
195
|
+
preActionModel: "cheap",
|
|
196
|
+
skillModel: "balanced",
|
|
197
|
+
postActionModel: "deep"
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
]
|
|
198
|
-
}
|
|
201
|
+
},
|
|
202
|
+
metadata: {
|
|
203
|
+
graphEntry: {
|
|
204
|
+
dataFilters: {
|
|
205
|
+
version: "exellix.structured-data-filters/v1",
|
|
206
|
+
when: { path: "runtime.input.tenant", op: "exists" }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
jobKnowledge: ["kb:job"]
|
|
199
211
|
});
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
assert.equal(migrated.id, "graph:legacy");
|
|
213
|
+
assert.equal(migrated.graph.nodes.length, 2);
|
|
214
|
+
assert.equal(migrated.graph.nodes[0]?.kind, "task");
|
|
215
|
+
assert.equal((migrated.graph.nodes[0]?.parameters).skillKey, "professional-answer");
|
|
216
|
+
const conditions = (migrated.graph.nodes[0]?.parameters)
|
|
217
|
+
.conditions;
|
|
218
|
+
assert.ok(Array.isArray(conditions.runWhen));
|
|
219
|
+
const smartInput = (migrated.graph.nodes[0]?.parameters)
|
|
220
|
+
.smartInput;
|
|
221
|
+
assert.deepEqual(smartInput?.paths, [{ path: "runtime.input.question" }]);
|
|
222
|
+
const profile = migrated.graph.metadata?.extensions?.["graphenix.executable/v1"];
|
|
223
|
+
const triplet = profile?.modelConfig?.cases?.[0]?.modelConfig;
|
|
224
|
+
assert.equal(triplet?.preActionModel?.kind, "profileChoice");
|
|
225
|
+
assert.equal(triplet?.preActionModel?.key, "cheap/default");
|
|
226
|
+
assert.equal(triplet?.skillModel?.key, "vol/default");
|
|
227
|
+
assert.equal(triplet?.postActionModel?.key, "deep/openai_deep");
|
|
228
|
+
assert.equal(validateAuthoringExecutableGraph(migrated).valid, true);
|
|
202
229
|
});
|
|
203
230
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const doc =
|
|
231
|
+
describe("node kinds", () => {
|
|
232
|
+
it("task nodes must use kind task", () => {
|
|
233
|
+
const doc = createMinimalExecutableGraph();
|
|
207
234
|
doc.graph.nodes[0].kind = "task:wrong";
|
|
208
|
-
const result =
|
|
209
|
-
|
|
235
|
+
const result = validateExecutableGraph(doc);
|
|
236
|
+
assert.ok(result.errors.some((e) => e.code === "TASK_NODE_INVALID"));
|
|
210
237
|
});
|
|
211
238
|
});
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const errors =
|
|
239
|
+
describe("deterministic cases", () => {
|
|
240
|
+
it("AC-001: runtime.mode eq simulate is accepted", () => {
|
|
241
|
+
const errors = validateCaseCondition({
|
|
215
242
|
path: "runtime.mode",
|
|
216
243
|
op: "eq",
|
|
217
244
|
value: "simulate"
|
|
218
245
|
});
|
|
219
|
-
|
|
246
|
+
assert.equal(errors.length, 0);
|
|
220
247
|
});
|
|
221
|
-
|
|
222
|
-
const doc = setGraphModelConfig(
|
|
248
|
+
it("AC-007/008: first matching case is selected, else default", () => {
|
|
249
|
+
const doc = setGraphModelConfig(createMinimalExecutableGraph(), {
|
|
223
250
|
version: "graph-model-config/v1",
|
|
224
251
|
cases: [
|
|
225
252
|
{
|
|
@@ -241,12 +268,12 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
241
268
|
}
|
|
242
269
|
]
|
|
243
270
|
});
|
|
244
|
-
const context =
|
|
245
|
-
const plan =
|
|
246
|
-
|
|
271
|
+
const context = buildDeterministicCaseContext(doc, { mode: "simulate" });
|
|
272
|
+
const plan = resolveNodeAiPlan(doc, "node:professional-answer", context);
|
|
273
|
+
assert.equal(plan.graphCaseId, "simulate");
|
|
247
274
|
});
|
|
248
|
-
|
|
249
|
-
const noDefault =
|
|
275
|
+
it("AC-009/010: default case requirements", () => {
|
|
276
|
+
const noDefault = validateExecutableGraph(setGraphModelConfig(createMinimalExecutableGraph(), {
|
|
250
277
|
version: "graph-model-config/v1",
|
|
251
278
|
cases: [
|
|
252
279
|
{
|
|
@@ -260,51 +287,51 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
260
287
|
}
|
|
261
288
|
]
|
|
262
289
|
}));
|
|
263
|
-
|
|
290
|
+
assert.ok(noDefault.errors.some((e) => e.code === "CASE_SELECTION_DEFAULT_MISSING"));
|
|
264
291
|
});
|
|
265
292
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const errors =
|
|
269
|
-
|
|
293
|
+
describe("forbidden AI selectors", () => {
|
|
294
|
+
it("AC-020: ai.prompt is rejected", () => {
|
|
295
|
+
const errors = validateCaseCondition({ ai: { prompt: "complex?" } });
|
|
296
|
+
assert.ok(errors.some((e) => e.code === "CASE_CONDITION_FORBIDDEN_AI_SELECTOR"));
|
|
270
297
|
});
|
|
271
|
-
|
|
272
|
-
const errors =
|
|
298
|
+
it("AC-024: ai.classification path is rejected", () => {
|
|
299
|
+
const errors = validateCaseCondition({
|
|
273
300
|
path: "ai.classification.risk",
|
|
274
301
|
op: "eq",
|
|
275
302
|
value: "high"
|
|
276
303
|
});
|
|
277
|
-
|
|
304
|
+
assert.ok(errors.some((e) => e.code === "CASE_CONDITION_FORBIDDEN_PATH"));
|
|
278
305
|
});
|
|
279
|
-
|
|
280
|
-
const errors =
|
|
306
|
+
it("AC-025: node.output path is rejected", () => {
|
|
307
|
+
const errors = validateCaseCondition({
|
|
281
308
|
path: "node.output.riskClassifier.risk",
|
|
282
309
|
op: "eq",
|
|
283
310
|
value: "high"
|
|
284
311
|
});
|
|
285
|
-
|
|
312
|
+
assert.ok(errors.some((e) => e.code === "CASE_CONDITION_FORBIDDEN_PATH"));
|
|
286
313
|
});
|
|
287
314
|
});
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
});
|
|
295
|
-
|
|
315
|
+
describe("profile-choice validation", () => {
|
|
316
|
+
it("AC-030/031/032: valid profileChoice keys accepted", () => {
|
|
317
|
+
assert.equal(isProfileChoiceKeyFormat("cheap/default"), true);
|
|
318
|
+
assert.equal(isProfileChoiceKeyFormat("vol/default"), true);
|
|
319
|
+
assert.equal(isProfileChoiceKeyFormat("deep/openai_deep"), true);
|
|
320
|
+
assert.equal(isKnownProfileChoice("vol/pro"), true);
|
|
321
|
+
});
|
|
322
|
+
it("AC-033/034/035/036: bare aliases rejected", () => {
|
|
296
323
|
for (const key of ["cheap", "balanced", "cheapest", "default"]) {
|
|
297
|
-
|
|
324
|
+
assert.equal(isProfileChoiceKeyFormat(key), false);
|
|
298
325
|
}
|
|
299
326
|
});
|
|
300
|
-
|
|
327
|
+
it("AC-037/038: vendor slugs and legacy aliases rejected", () => {
|
|
301
328
|
// Syntactically valid profile/choice shape, but not a bundled registry key.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
329
|
+
assert.equal(isProfileChoiceKeyFormat("openai/gpt-4o-mini"), true);
|
|
330
|
+
assert.equal(isKnownProfileChoice("openai/gpt-4o-mini"), false);
|
|
331
|
+
assert.equal(isProfileChoiceKeyFormat("cheap@anthropic_cheap"), false);
|
|
305
332
|
});
|
|
306
|
-
|
|
307
|
-
const result =
|
|
333
|
+
it("AC-039: unknown choice rejected in graph validation", () => {
|
|
334
|
+
const result = validateExecutableGraph(setGraphModelConfig(createMinimalExecutableGraph(), {
|
|
308
335
|
version: "graph-model-config/v1",
|
|
309
336
|
cases: [
|
|
310
337
|
{
|
|
@@ -317,10 +344,10 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
317
344
|
}
|
|
318
345
|
]
|
|
319
346
|
}));
|
|
320
|
-
|
|
347
|
+
assert.ok(result.errors.some((e) => e.code === "PROFILE_CHOICE_KEY_UNKNOWN"));
|
|
321
348
|
});
|
|
322
|
-
|
|
323
|
-
const result =
|
|
349
|
+
it("deprecated kind profile is rejected", () => {
|
|
350
|
+
const result = validateExecutableGraph(setGraphModelConfig(createMinimalExecutableGraph(), {
|
|
324
351
|
version: "graph-model-config/v1",
|
|
325
352
|
cases: [
|
|
326
353
|
{
|
|
@@ -333,13 +360,13 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
333
360
|
}
|
|
334
361
|
]
|
|
335
362
|
}));
|
|
336
|
-
|
|
363
|
+
assert.ok(result.errors.some((e) => e.code === "PROFILE_SELECTION_KIND_DEPRECATED"));
|
|
337
364
|
});
|
|
338
365
|
});
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const doc =
|
|
342
|
-
const ext = doc.graph.metadata.extensions[
|
|
366
|
+
describe("normalization", () => {
|
|
367
|
+
it("AC-060: graph default shorthand normalizes to cases[0].id = default", () => {
|
|
368
|
+
const doc = createMinimalExecutableGraph();
|
|
369
|
+
const ext = doc.graph.metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
|
|
343
370
|
ext.modelConfig = {
|
|
344
371
|
version: "graph-model-config/v1",
|
|
345
372
|
default: {
|
|
@@ -348,25 +375,25 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
348
375
|
postActionModel: { kind: "profileChoice", key: "cheap/default" }
|
|
349
376
|
}
|
|
350
377
|
};
|
|
351
|
-
const normalized =
|
|
352
|
-
const profile = normalized.graph.metadata?.extensions?.[
|
|
353
|
-
|
|
354
|
-
|
|
378
|
+
const normalized = normalizeExecutableGraph(doc);
|
|
379
|
+
const profile = normalized.graph.metadata?.extensions?.[EXECUTABLE_PROFILE_NAMESPACE];
|
|
380
|
+
assert.equal(profile?.modelConfig?.cases?.[0]?.id, "default");
|
|
381
|
+
assert.equal(profile?.modelConfig?.cases?.[0]?.when, undefined);
|
|
355
382
|
});
|
|
356
|
-
|
|
357
|
-
const doc =
|
|
383
|
+
it("AC-066: normalization does not mutate source graph", () => {
|
|
384
|
+
const doc = createMinimalExecutableGraph();
|
|
358
385
|
const before = structuredClone(doc);
|
|
359
|
-
|
|
360
|
-
|
|
386
|
+
normalizeExecutableGraph(doc);
|
|
387
|
+
assert.deepEqual(doc, before);
|
|
361
388
|
});
|
|
362
389
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const doc =
|
|
390
|
+
describe("full resolution example", () => {
|
|
391
|
+
it("matches CR/FR section 12 resolution", () => {
|
|
392
|
+
const doc = createMinimalExecutableGraph();
|
|
366
393
|
const taskNode = doc.graph.nodes.find((n) => n.id === "node:professional-answer");
|
|
367
394
|
const params = taskNode.parameters;
|
|
368
|
-
|
|
369
|
-
const ext = doc.graph.metadata.extensions[
|
|
395
|
+
assert.ok(params && params.nodeType === "task");
|
|
396
|
+
const ext = doc.graph.metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
|
|
370
397
|
ext.modelConfig = {
|
|
371
398
|
version: "graph-model-config/v1",
|
|
372
399
|
cases: [
|
|
@@ -439,51 +466,51 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
439
466
|
}
|
|
440
467
|
}
|
|
441
468
|
};
|
|
442
|
-
const context =
|
|
469
|
+
const context = buildDeterministicCaseContext(doc, {
|
|
443
470
|
mode: "live",
|
|
444
471
|
input: { analysisDepth: "deep", riskLevel: "high" }
|
|
445
472
|
});
|
|
446
|
-
const plan =
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
473
|
+
const plan = resolveNodeAiPlan(doc, "node:professional-answer", context);
|
|
474
|
+
assert.equal(plan.graphCaseId, "deep-live");
|
|
475
|
+
assert.equal(plan.nodeCaseId, "high-risk-input");
|
|
476
|
+
assert.equal(plan.slots.preActionModel.selected.kind, "profileChoice");
|
|
450
477
|
if (plan.slots.preActionModel.selected.kind === "profileChoice") {
|
|
451
|
-
|
|
478
|
+
assert.equal(plan.slots.preActionModel.selected.key, "vol/default");
|
|
452
479
|
}
|
|
453
480
|
if (plan.slots.skillModel.selected.kind === "profileChoice") {
|
|
454
|
-
|
|
481
|
+
assert.equal(plan.slots.skillModel.selected.key, "deep/openai_deep");
|
|
455
482
|
}
|
|
456
|
-
|
|
457
|
-
|
|
483
|
+
assert.ok(plan.caseSelection.graph.matchedConditions.some((c) => c.includes("runtime.mode eq live")));
|
|
484
|
+
assert.ok(plan.caseSelection.node?.matchedConditions.some((c) => c.includes("runtime.input.riskLevel eq high")));
|
|
458
485
|
});
|
|
459
486
|
});
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const context =
|
|
487
|
+
describe("evaluateCaseCondition", () => {
|
|
488
|
+
it("AC-004/005/006: all, any, not supported", () => {
|
|
489
|
+
const context = buildDeterministicCaseContext(createMinimalExecutableGraph(), {
|
|
463
490
|
mode: "live",
|
|
464
491
|
input: { riskLevel: "high", tier: "gold" }
|
|
465
492
|
});
|
|
466
|
-
|
|
493
|
+
assert.equal(evaluateCaseCondition({
|
|
467
494
|
all: [
|
|
468
495
|
{ path: "runtime.mode", op: "eq", value: "live" },
|
|
469
496
|
{ path: "runtime.input.riskLevel", op: "eq", value: "high" }
|
|
470
497
|
]
|
|
471
498
|
}, context), true);
|
|
472
|
-
|
|
499
|
+
assert.equal(evaluateCaseCondition({
|
|
473
500
|
any: [
|
|
474
501
|
{ path: "runtime.input.tier", op: "eq", value: "silver" },
|
|
475
502
|
{ path: "runtime.input.tier", op: "eq", value: "gold" }
|
|
476
503
|
]
|
|
477
504
|
}, context), true);
|
|
478
|
-
|
|
505
|
+
assert.equal(evaluateCaseCondition({
|
|
479
506
|
not: { path: "runtime.mode", op: "eq", value: "simulate" }
|
|
480
507
|
}, context), true);
|
|
481
508
|
});
|
|
482
509
|
});
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const doc =
|
|
486
|
-
const ext = doc.graph.metadata.extensions[
|
|
510
|
+
describe("lifecycle artifacts", () => {
|
|
511
|
+
it("AC-A001/A005: authoring graph with modelConfig.default passes", () => {
|
|
512
|
+
const doc = createMinimalExecutableGraph();
|
|
513
|
+
const ext = doc.graph.metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
|
|
487
514
|
ext.modelConfig = {
|
|
488
515
|
version: "graph-model-config/v1",
|
|
489
516
|
default: {
|
|
@@ -492,10 +519,10 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
492
519
|
postActionModel: { kind: "profileChoice", key: "cheap/default" }
|
|
493
520
|
}
|
|
494
521
|
};
|
|
495
|
-
|
|
522
|
+
assert.equal(validateAuthoringGraph(doc).valid, true);
|
|
496
523
|
});
|
|
497
|
-
|
|
498
|
-
const bareProfile =
|
|
524
|
+
it("AC-A003/A004: bare profile and kind profile fail authoring validation", () => {
|
|
525
|
+
const bareProfile = validateAuthoringGraph(setGraphModelConfig(createMinimalExecutableGraph(), {
|
|
499
526
|
version: "graph-model-config/v1",
|
|
500
527
|
cases: [
|
|
501
528
|
{
|
|
@@ -508,8 +535,8 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
508
535
|
}
|
|
509
536
|
]
|
|
510
537
|
}));
|
|
511
|
-
|
|
512
|
-
const kindProfile =
|
|
538
|
+
assert.ok(bareProfile.errors.some((e) => e.code === "PROFILE_CHOICE_KEY_FORMAT_INVALID"));
|
|
539
|
+
const kindProfile = validateAuthoringGraph(setGraphModelConfig(createMinimalExecutableGraph(), {
|
|
513
540
|
version: "graph-model-config/v1",
|
|
514
541
|
cases: [
|
|
515
542
|
{
|
|
@@ -522,11 +549,11 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
522
549
|
}
|
|
523
550
|
]
|
|
524
551
|
}));
|
|
525
|
-
|
|
552
|
+
assert.ok(kindProfile.errors.some((e) => e.code === "PROFILE_SELECTION_KIND_DEPRECATED"));
|
|
526
553
|
});
|
|
527
|
-
|
|
528
|
-
const doc =
|
|
529
|
-
const ext = doc.graph.metadata.extensions[
|
|
554
|
+
it("AC-P001/P003/P008/P009/P010: compile produces run-bound executable plan", () => {
|
|
555
|
+
const doc = createMinimalExecutableGraph();
|
|
556
|
+
const ext = doc.graph.metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
|
|
530
557
|
ext.modelConfig = {
|
|
531
558
|
version: "graph-model-config/v1",
|
|
532
559
|
cases: [
|
|
@@ -549,57 +576,57 @@ function setGraphModelConfig(doc, modelConfig) {
|
|
|
549
576
|
}
|
|
550
577
|
]
|
|
551
578
|
};
|
|
552
|
-
const runtime =
|
|
579
|
+
const runtime = buildRuntimeObject({
|
|
553
580
|
jobId: "job-compile-1",
|
|
554
581
|
mode: "simulate",
|
|
555
582
|
input: { target_subnet_cidr: "10.0.0.0/24" }
|
|
556
583
|
});
|
|
557
584
|
const before = structuredClone(doc);
|
|
558
|
-
const plan =
|
|
585
|
+
const plan = compileExecutablePlan(doc, runtime, {
|
|
559
586
|
profileRegistry: { version: "3.0.0", registryHash: "sha256:profiles456" },
|
|
560
587
|
environment: "prod"
|
|
561
588
|
});
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const profileExt = plan.graph.graph.metadata?.extensions?.[
|
|
569
|
-
|
|
589
|
+
assert.deepEqual(doc, before);
|
|
590
|
+
assert.equal(plan.format, "graphenix.executable-plan/v1");
|
|
591
|
+
assert.equal(plan.source.graphId, doc.id);
|
|
592
|
+
assert.equal(plan.runtimeBinding.jobId, "job-compile-1");
|
|
593
|
+
assert.equal(plan.caseSelection.graph.caseId, "simulate");
|
|
594
|
+
assert.equal(plan.profileRegistry.version, "3.0.0");
|
|
595
|
+
const profileExt = plan.graph.graph.metadata?.extensions?.[EXECUTABLE_PROFILE_NAMESPACE];
|
|
596
|
+
assert.equal("default" in (profileExt?.modelConfig ?? {}), false);
|
|
570
597
|
const professionalPlan = plan.nodePlans["node:professional-answer"];
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
const doc =
|
|
576
|
-
const runtime =
|
|
577
|
-
const plan =
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
const doc =
|
|
584
|
-
const runtime =
|
|
585
|
-
const plan =
|
|
586
|
-
const trace =
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
598
|
+
assert.equal(professionalPlan?.modelSlots?.skillModel?.selection.kind, "profileChoice");
|
|
599
|
+
assert.equal(validateExecutablePlan(plan).valid, true);
|
|
600
|
+
});
|
|
601
|
+
it("AC-E003/E006/E008: executable plan is normalized with resolved node slots", () => {
|
|
602
|
+
const doc = createMinimalExecutableGraph();
|
|
603
|
+
const runtime = buildRuntimeObject({ jobId: "job-plan-1" });
|
|
604
|
+
const plan = compileExecutablePlan(doc, runtime);
|
|
605
|
+
assert.equal(validateExecutablePlan(plan).valid, true);
|
|
606
|
+
assert.ok(plan.nodePlans["node:professional-answer"]?.modelSlots?.preActionModel);
|
|
607
|
+
assert.equal("credentials" in plan, false);
|
|
608
|
+
});
|
|
609
|
+
it("AC-T001/T002/T011: execution trace references plan and keeps append-only events", () => {
|
|
610
|
+
const doc = createMinimalExecutableGraph();
|
|
611
|
+
const runtime = buildRuntimeObject({ jobId: "job-trace-1", mode: "live" });
|
|
612
|
+
const plan = compileExecutablePlan(doc, runtime);
|
|
613
|
+
const trace = createExecutionTrace(plan, runtime);
|
|
614
|
+
assert.equal(trace.source.graphId, plan.source.graphId);
|
|
615
|
+
assert.equal(trace.plan.planId, plan.planId);
|
|
616
|
+
assert.equal(trace.status, "queued");
|
|
590
617
|
const startedAt = "2026-06-06T12:00:01.000Z";
|
|
591
|
-
const updated =
|
|
618
|
+
const updated = appendExecutionEvent(trace, {
|
|
592
619
|
id: "evt:1",
|
|
593
620
|
ts: startedAt,
|
|
594
621
|
level: "info",
|
|
595
622
|
type: "graph.started",
|
|
596
623
|
message: "Graph execution started."
|
|
597
624
|
});
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
625
|
+
assert.equal(updated.events.length, 1);
|
|
626
|
+
assert.notEqual(updated, trace);
|
|
627
|
+
assert.equal(validateExecutionTrace(updated, plan.nodePlans).valid, true);
|
|
601
628
|
const nodeTrace = updated.nodeExecutions["node:professional-answer"];
|
|
602
|
-
|
|
629
|
+
assert.ok(nodeTrace?.units["unit:node:professional-answer:skill"]);
|
|
603
630
|
});
|
|
604
631
|
});
|
|
605
632
|
function getTaskNode(doc, nodeId = "node:professional-answer") {
|
|
@@ -608,7 +635,7 @@ function getTaskNode(doc, nodeId = "node:professional-answer") {
|
|
|
608
635
|
function setTaskConfiguration(doc, config) {
|
|
609
636
|
const node = getTaskNode(doc);
|
|
610
637
|
const params = node.parameters;
|
|
611
|
-
|
|
638
|
+
assert.ok(params && params.nodeType === "task");
|
|
612
639
|
node.parameters = {
|
|
613
640
|
...params,
|
|
614
641
|
taskConfiguration: config
|
|
@@ -616,22 +643,22 @@ function setTaskConfiguration(doc, config) {
|
|
|
616
643
|
return doc;
|
|
617
644
|
}
|
|
618
645
|
function setGraphPolicies(doc, policies) {
|
|
619
|
-
const ext = doc.graph.metadata.extensions[
|
|
646
|
+
const ext = doc.graph.metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
|
|
620
647
|
ext.policies = policies;
|
|
621
648
|
return doc;
|
|
622
649
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const doc =
|
|
626
|
-
const runtime =
|
|
627
|
-
const plan =
|
|
650
|
+
describe("execution units", () => {
|
|
651
|
+
it("AC-001: task node with no preActions/postActions compiles into one skill unit", () => {
|
|
652
|
+
const doc = createMinimalExecutableGraph();
|
|
653
|
+
const runtime = buildRuntimeObject({ jobId: "job-units-1" });
|
|
654
|
+
const plan = compileExecutablePlan(doc, runtime);
|
|
628
655
|
const units = plan.nodePlans["node:professional-answer"].executionUnits;
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
656
|
+
assert.equal(units.length, 1);
|
|
657
|
+
assert.equal(units[0].unitKind, "skill");
|
|
658
|
+
assert.equal(units[0].unitId, "unit:node:professional-answer:skill");
|
|
632
659
|
});
|
|
633
|
-
|
|
634
|
-
const doc = setTaskConfiguration(
|
|
660
|
+
it("AC-002/003/004: preActions before skill and postActions after with stable order", () => {
|
|
661
|
+
const doc = setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
635
662
|
preActions: [
|
|
636
663
|
{ id: "pre:a", actionKey: "prepare-input" },
|
|
637
664
|
{ id: "pre:b", actionKey: "normalize-input" }
|
|
@@ -641,10 +668,10 @@ function setGraphPolicies(doc, policies) {
|
|
|
641
668
|
{ id: "post:b", actionKey: "shape-response" }
|
|
642
669
|
]
|
|
643
670
|
});
|
|
644
|
-
const plan =
|
|
671
|
+
const plan = compileExecutablePlan(doc, buildRuntimeObject({ jobId: "job-units-2" }));
|
|
645
672
|
const units = plan.nodePlans["node:professional-answer"].executionUnits;
|
|
646
|
-
|
|
647
|
-
|
|
673
|
+
assert.equal(units.length, 5);
|
|
674
|
+
assert.deepEqual(units.map((unit) => [unit.order, unit.unitKind, unit.actionKey ?? unit.skillKey]), [
|
|
648
675
|
[0, "preAction", "prepare-input"],
|
|
649
676
|
[1, "preAction", "normalize-input"],
|
|
650
677
|
[2, "skill", "professional-answer"],
|
|
@@ -652,31 +679,31 @@ function setGraphPolicies(doc, policies) {
|
|
|
652
679
|
[4, "postAction", "shape-response"]
|
|
653
680
|
]);
|
|
654
681
|
});
|
|
655
|
-
|
|
656
|
-
const doc = setTaskConfiguration(
|
|
682
|
+
it("AC-005/006/007: units use resolved node model slots", () => {
|
|
683
|
+
const doc = setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
657
684
|
preActions: [{ id: "pre:a", actionKey: "prepare-input" }],
|
|
658
685
|
postActions: [{ id: "post:a", actionKey: "validate-output" }]
|
|
659
686
|
});
|
|
660
|
-
const plan =
|
|
687
|
+
const plan = compileExecutablePlan(doc, buildRuntimeObject({ jobId: "job-units-3" }));
|
|
661
688
|
const nodePlan = plan.nodePlans["node:professional-answer"];
|
|
662
689
|
const pre = nodePlan.executionUnits.find((unit) => unit.unitKind === "preAction");
|
|
663
690
|
const skill = nodePlan.executionUnits.find((unit) => unit.unitKind === "skill");
|
|
664
691
|
const post = nodePlan.executionUnits.find((unit) => unit.unitKind === "postAction");
|
|
665
|
-
|
|
692
|
+
assert.equal(pre.modelSlot, "preActionModel");
|
|
666
693
|
if (pre.modelSelection?.kind === "profileChoice") {
|
|
667
|
-
|
|
694
|
+
assert.equal(pre.modelSelection.key, "cheap/default");
|
|
668
695
|
}
|
|
669
|
-
|
|
696
|
+
assert.equal(skill.modelSlot, "skillModel");
|
|
670
697
|
if (skill.modelSelection?.kind === "profileChoice") {
|
|
671
|
-
|
|
698
|
+
assert.equal(skill.modelSelection.key, "vol/default");
|
|
672
699
|
}
|
|
673
|
-
|
|
700
|
+
assert.equal(post.modelSlot, "postActionModel");
|
|
674
701
|
if (post.modelSelection?.kind === "profileChoice") {
|
|
675
|
-
|
|
702
|
+
assert.equal(post.modelSelection.key, "cheap/default");
|
|
676
703
|
}
|
|
677
704
|
});
|
|
678
|
-
|
|
679
|
-
const doc = setTaskConfiguration(
|
|
705
|
+
it("AC-008/009: node skillModel override does not affect pre/post units", () => {
|
|
706
|
+
const doc = setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
680
707
|
modelConfig: {
|
|
681
708
|
inherit: true,
|
|
682
709
|
modelConfig: {
|
|
@@ -686,23 +713,23 @@ function setGraphPolicies(doc, policies) {
|
|
|
686
713
|
preActions: [{ id: "pre:a", actionKey: "prepare-input" }],
|
|
687
714
|
postActions: [{ id: "post:a", actionKey: "validate-output" }]
|
|
688
715
|
});
|
|
689
|
-
const plan =
|
|
716
|
+
const plan = compileExecutablePlan(doc, buildRuntimeObject({ jobId: "job-units-4" }));
|
|
690
717
|
const units = plan.nodePlans["node:professional-answer"].executionUnits;
|
|
691
718
|
const pre = units.find((unit) => unit.unitKind === "preAction");
|
|
692
719
|
const skill = units.find((unit) => unit.unitKind === "skill");
|
|
693
720
|
const post = units.find((unit) => unit.unitKind === "postAction");
|
|
694
721
|
if (pre.modelSelection?.kind === "profileChoice") {
|
|
695
|
-
|
|
722
|
+
assert.equal(pre.modelSelection.key, "cheap/default");
|
|
696
723
|
}
|
|
697
724
|
if (skill.modelSelection?.kind === "profileChoice") {
|
|
698
|
-
|
|
725
|
+
assert.equal(skill.modelSelection.key, "deep/openai_deep");
|
|
699
726
|
}
|
|
700
727
|
if (post.modelSelection?.kind === "profileChoice") {
|
|
701
|
-
|
|
728
|
+
assert.equal(post.modelSelection.key, "cheap/default");
|
|
702
729
|
}
|
|
703
730
|
});
|
|
704
|
-
|
|
705
|
-
const doc = setTaskConfiguration(
|
|
731
|
+
it("AC-010: action-level model override fails when policy disabled", () => {
|
|
732
|
+
const doc = setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
706
733
|
preActions: [
|
|
707
734
|
{
|
|
708
735
|
id: "pre:a",
|
|
@@ -711,11 +738,11 @@ function setGraphPolicies(doc, policies) {
|
|
|
711
738
|
}
|
|
712
739
|
]
|
|
713
740
|
});
|
|
714
|
-
const result =
|
|
715
|
-
|
|
741
|
+
const result = validateAuthoringGraph(doc);
|
|
742
|
+
assert.ok(result.errors.some((e) => e.code === "TASK_ACTION_MODEL_OVERRIDE_FORBIDDEN"));
|
|
716
743
|
});
|
|
717
|
-
|
|
718
|
-
const doc = setGraphPolicies(setTaskConfiguration(
|
|
744
|
+
it("AC-011/012: action-level model override passes when policy enabled", () => {
|
|
745
|
+
const doc = setGraphPolicies(setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
719
746
|
preActions: [
|
|
720
747
|
{
|
|
721
748
|
id: "pre:a",
|
|
@@ -724,16 +751,16 @@ function setGraphPolicies(doc, policies) {
|
|
|
724
751
|
}
|
|
725
752
|
]
|
|
726
753
|
}), { allowActionLevelModelOverride: true });
|
|
727
|
-
|
|
728
|
-
const plan =
|
|
754
|
+
assert.equal(validateAuthoringGraph(doc).valid, true);
|
|
755
|
+
const plan = compileExecutablePlan(doc, buildRuntimeObject({ jobId: "job-units-5" }));
|
|
729
756
|
const pre = plan.nodePlans["node:professional-answer"].executionUnits[0];
|
|
730
|
-
|
|
757
|
+
assert.equal(pre.modelSource, "actionOverride");
|
|
731
758
|
if (pre.modelSelection?.kind === "profileChoice") {
|
|
732
|
-
|
|
759
|
+
assert.equal(pre.modelSelection.key, "vol/pro");
|
|
733
760
|
}
|
|
734
761
|
});
|
|
735
|
-
|
|
736
|
-
const doc = setGraphPolicies(setTaskConfiguration(
|
|
762
|
+
it("AC-013: bare profile key in action-level override fails", () => {
|
|
763
|
+
const doc = setGraphPolicies(setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
737
764
|
preActions: [
|
|
738
765
|
{
|
|
739
766
|
id: "pre:a",
|
|
@@ -742,11 +769,11 @@ function setGraphPolicies(doc, policies) {
|
|
|
742
769
|
}
|
|
743
770
|
]
|
|
744
771
|
}), { allowActionLevelModelOverride: true });
|
|
745
|
-
const result =
|
|
746
|
-
|
|
772
|
+
const result = validateAuthoringGraph(doc);
|
|
773
|
+
assert.ok(result.errors.some((e) => e.code === "PROFILE_CHOICE_KEY_FORMAT_INVALID"));
|
|
747
774
|
});
|
|
748
|
-
|
|
749
|
-
const resolvedPre =
|
|
775
|
+
it("AC-014/015/016: unit fallback stays on same slot only", () => {
|
|
776
|
+
const resolvedPre = resolveNodeModelSlot({
|
|
750
777
|
slot: "preActionModel",
|
|
751
778
|
graphSelection: { kind: "profileChoice", key: "cheap/default" },
|
|
752
779
|
nodeSelection: { kind: "profileChoice", key: "vol/pro" },
|
|
@@ -762,29 +789,29 @@ function setGraphPolicies(doc, policies) {
|
|
|
762
789
|
graphCaseId: "default",
|
|
763
790
|
nodeId: "node:professional-answer"
|
|
764
791
|
});
|
|
765
|
-
|
|
792
|
+
assert.equal(resolvedPre.source, "fallbackToGraphDefault");
|
|
766
793
|
if (resolvedPre.selected.kind === "profileChoice") {
|
|
767
|
-
|
|
794
|
+
assert.equal(resolvedPre.selected.key, "cheap/default");
|
|
768
795
|
}
|
|
769
|
-
const plan =
|
|
796
|
+
const plan = compileExecutablePlan(setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
770
797
|
preActions: [{ id: "pre:a", actionKey: "prepare-input" }],
|
|
771
798
|
postActions: [{ id: "post:a", actionKey: "validate-output" }]
|
|
772
|
-
}),
|
|
799
|
+
}), buildRuntimeObject({ jobId: "job-units-fallback" }));
|
|
773
800
|
const units = plan.nodePlans["node:professional-answer"].executionUnits;
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
801
|
+
assert.ok(units.every((unit) => unit.modelSlot !== "skillModel" || unit.unitKind === "skill"));
|
|
802
|
+
assert.ok(units.every((unit) => unit.modelSlot !== "postActionModel" || unit.unitKind === "postAction"));
|
|
803
|
+
assert.ok(units.every((unit) => unit.modelSlot !== "preActionModel" || unit.unitKind === "preAction"));
|
|
777
804
|
});
|
|
778
|
-
|
|
779
|
-
const doc = setTaskConfiguration(
|
|
805
|
+
it("AC-017/018/019: execution trace records planned units and unit fallback", () => {
|
|
806
|
+
const doc = setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
780
807
|
preActions: [{ id: "pre:a", actionKey: "prepare-input" }],
|
|
781
808
|
postActions: [{ id: "post:a", actionKey: "validate-output" }]
|
|
782
809
|
});
|
|
783
|
-
const runtime =
|
|
784
|
-
const plan =
|
|
785
|
-
const trace =
|
|
810
|
+
const runtime = buildRuntimeObject({ jobId: "job-units-6" });
|
|
811
|
+
const plan = compileExecutablePlan(doc, runtime);
|
|
812
|
+
const trace = createExecutionTrace(plan, runtime);
|
|
786
813
|
const nodeTrace = trace.nodeExecutions["node:professional-answer"];
|
|
787
|
-
|
|
814
|
+
assert.equal(Object.keys(nodeTrace.units).length, 3);
|
|
788
815
|
const skillUnitId = "unit:node:professional-answer:skill";
|
|
789
816
|
nodeTrace.units[skillUnitId] = {
|
|
790
817
|
...nodeTrace.units[skillUnitId],
|
|
@@ -811,8 +838,8 @@ function setGraphPolicies(doc, policies) {
|
|
|
811
838
|
enabled: true,
|
|
812
839
|
allowedTriggers: ["nodeModelUnavailable"]
|
|
813
840
|
};
|
|
814
|
-
|
|
815
|
-
const withEvent =
|
|
841
|
+
assert.equal(validateExecutionTrace(invalidFallback, invalidPolicyPlan.nodePlans).valid, false);
|
|
842
|
+
const withEvent = appendExecutionEvent(trace, {
|
|
816
843
|
id: "evt:unit:1",
|
|
817
844
|
ts: "2026-06-06T12:00:02.100Z",
|
|
818
845
|
level: "info",
|
|
@@ -826,18 +853,18 @@ function setGraphPolicies(doc, policies) {
|
|
|
826
853
|
source: "graphDefault"
|
|
827
854
|
}
|
|
828
855
|
});
|
|
829
|
-
|
|
856
|
+
assert.equal(withEvent.events[0].type, "executionUnit.model.resolved");
|
|
830
857
|
});
|
|
831
|
-
|
|
832
|
-
const doc = setTaskConfiguration(
|
|
858
|
+
it("AC-020: graph, plan, and trace remain implementation-agnostic", () => {
|
|
859
|
+
const doc = setTaskConfiguration(createMinimalExecutableGraph(), {
|
|
833
860
|
preActions: [{ id: "pre:a", actionKey: "prepare-input" }]
|
|
834
861
|
});
|
|
835
|
-
const runtime =
|
|
836
|
-
const plan =
|
|
837
|
-
const trace =
|
|
862
|
+
const runtime = buildRuntimeObject({ jobId: "job-units-7" });
|
|
863
|
+
const plan = compileExecutablePlan(doc, runtime);
|
|
864
|
+
const trace = createExecutionTrace(plan, runtime);
|
|
838
865
|
const serialized = JSON.stringify({ doc, plan, trace });
|
|
839
866
|
for (const forbidden of ["openrouter", "anthropic-sdk", "langchain"]) {
|
|
840
|
-
|
|
867
|
+
assert.equal(serialized.includes(forbidden), false);
|
|
841
868
|
}
|
|
842
869
|
});
|
|
843
870
|
});
|