@exellix/graph-composer 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.env.example +66 -0
  2. package/LICENSE +21 -0
  3. package/README.md +329 -0
  4. package/dist/aiTaskProfile.d.ts +66 -0
  5. package/dist/aiTaskProfile.d.ts.map +1 -0
  6. package/dist/aiTaskProfile.js +179 -0
  7. package/dist/canonicalGraphDocument.d.ts +8 -0
  8. package/dist/canonicalGraphDocument.d.ts.map +1 -0
  9. package/dist/canonicalGraphDocument.js +344 -0
  10. package/dist/canonicalGraphWarnings.d.ts +6 -0
  11. package/dist/canonicalGraphWarnings.d.ts.map +1 -0
  12. package/dist/canonicalGraphWarnings.js +140 -0
  13. package/dist/catalogMatchAssist.d.ts +20 -0
  14. package/dist/catalogMatchAssist.d.ts.map +1 -0
  15. package/dist/catalogMatchAssist.js +203 -0
  16. package/dist/cataloxCatalogBridge.d.ts +103 -0
  17. package/dist/cataloxCatalogBridge.d.ts.map +1 -0
  18. package/dist/cataloxCatalogBridge.js +222 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +43 -0
  22. package/dist/composeInstructions.d.ts +11 -0
  23. package/dist/composeInstructions.d.ts.map +1 -0
  24. package/dist/composeInstructions.js +39 -0
  25. package/dist/defaultUtilitySkills.d.ts +4 -0
  26. package/dist/defaultUtilitySkills.d.ts.map +1 -0
  27. package/dist/defaultUtilitySkills.js +5 -0
  28. package/dist/exampleGeneration.d.ts +15 -0
  29. package/dist/exampleGeneration.d.ts.map +1 -0
  30. package/dist/exampleGeneration.js +72 -0
  31. package/dist/exampleInputs.d.ts +12 -0
  32. package/dist/exampleInputs.d.ts.map +1 -0
  33. package/dist/exampleInputs.js +181 -0
  34. package/dist/graphComposerActions.d.ts +22 -0
  35. package/dist/graphComposerActions.d.ts.map +1 -0
  36. package/dist/graphComposerActions.js +168 -0
  37. package/dist/graphComposerAgent.d.ts +26 -0
  38. package/dist/graphComposerAgent.d.ts.map +1 -0
  39. package/dist/graphComposerAgent.js +175 -0
  40. package/dist/graphComposerOutputValidation.d.ts +23 -0
  41. package/dist/graphComposerOutputValidation.d.ts.map +1 -0
  42. package/dist/graphComposerOutputValidation.js +709 -0
  43. package/dist/graphConceptPatchMerge.d.ts +10 -0
  44. package/dist/graphConceptPatchMerge.d.ts.map +1 -0
  45. package/dist/graphConceptPatchMerge.js +40 -0
  46. package/dist/graphEngineBridge.d.ts +7 -0
  47. package/dist/graphEngineBridge.d.ts.map +1 -0
  48. package/dist/graphEngineBridge.js +5 -0
  49. package/dist/index.d.ts +24 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +19 -0
  52. package/dist/openRouterConnectTimeout.d.ts +3 -0
  53. package/dist/openRouterConnectTimeout.d.ts.map +1 -0
  54. package/dist/openRouterConnectTimeout.js +48 -0
  55. package/dist/packDir.d.ts +7 -0
  56. package/dist/packDir.d.ts.map +1 -0
  57. package/dist/packDir.js +23 -0
  58. package/dist/parseGraphConceptStory.d.ts +21 -0
  59. package/dist/parseGraphConceptStory.d.ts.map +1 -0
  60. package/dist/parseGraphConceptStory.js +105 -0
  61. package/dist/redactForLog.d.ts +2 -0
  62. package/dist/redactForLog.d.ts.map +1 -0
  63. package/dist/redactForLog.js +37 -0
  64. package/dist/runGraphComposer.d.ts +54 -0
  65. package/dist/runGraphComposer.d.ts.map +1 -0
  66. package/dist/runGraphComposer.js +444 -0
  67. package/dist/scopingCatalogHostTypes.d.ts +28 -0
  68. package/dist/scopingCatalogHostTypes.d.ts.map +1 -0
  69. package/dist/scopingCatalogHostTypes.js +6 -0
  70. package/dist/scopingNeedMatchAssist.d.ts +14 -0
  71. package/dist/scopingNeedMatchAssist.d.ts.map +1 -0
  72. package/dist/scopingNeedMatchAssist.js +58 -0
  73. package/dist/taskNodeTaskVariable.d.ts +44 -0
  74. package/dist/taskNodeTaskVariable.d.ts.map +1 -0
  75. package/dist/taskNodeTaskVariable.js +347 -0
  76. package/dist/types.d.ts +174 -0
  77. package/dist/types.d.ts.map +1 -0
  78. package/dist/types.js +1 -0
  79. package/examples/network-vuln-subnet-triage.v2.json +389 -0
  80. package/functions/graph-composer/meta.json +607 -0
  81. package/functions/graph-composer/prompts/README.md +46 -0
  82. package/functions/graph-composer/prompts/action-create.md +51 -0
  83. package/functions/graph-composer/prompts/action-explain.md +26 -0
  84. package/functions/graph-composer/prompts/action-modify.md +32 -0
  85. package/functions/graph-composer/prompts/action-review-concept.md +97 -0
  86. package/functions/graph-composer/prompts/action-suggest-catalog-creations.md +31 -0
  87. package/functions/graph-composer/prompts/action-suggest-catalog-resolution.md +42 -0
  88. package/functions/graph-composer/prompts/action-suggest-concept-objective.md +38 -0
  89. package/functions/graph-composer/prompts/action-suggest-scoping-map-creation.md +31 -0
  90. package/functions/graph-composer/prompts/action-suggest-scoping-need-match.md +25 -0
  91. package/functions/graph-composer/prompts/default-utility-skills.json +22 -0
  92. package/functions/graph-composer/prompts/judge-rules.md +30 -0
  93. package/functions/graph-composer/prompts/orchestrator-system.md +21 -0
  94. package/functions/graph-composer/prompts/shared/graph-format.md +124 -0
  95. package/functions/graph-composer/prompts/shared/request-context.md +12 -0
  96. package/functions/graph-composer/prompts/shared/skill-selection.md +6 -0
  97. package/functions/graph-composer/prompts/shared/structural-validation.md +19 -0
  98. package/functions/graph-composer/prompts/skill-catalog-ai-header.md +3 -0
  99. package/functions/graph-composer/prompts/skill-catalog-utility-header.md +3 -0
  100. package/functions/graph-composer/prompts/skill-mode-extensible.md +7 -0
  101. package/functions/graph-composer/prompts/skill-mode-locked.md +7 -0
  102. package/functions/graph-composer/test-cases.json +52 -0
  103. package/package.json +86 -0
@@ -0,0 +1,179 @@
1
+ /** Built-in worox-graph local skills — no aiTaskProfile required. */
2
+ export const DEFAULT_LOCAL_SKILL_KEYS = [
3
+ "scoped-data-reader",
4
+ "deterministic-rule",
5
+ "scoped-answer-assembler",
6
+ "scoped-answer-writer",
7
+ ];
8
+ const VALID_SYNTHESIS_DESTINATIONS = new Set([
9
+ "job",
10
+ "task",
11
+ "execution",
12
+ ]);
13
+ function nonEmptyString(v) {
14
+ return typeof v === "string" && v.trim().length > 0;
15
+ }
16
+ function readRecord(v) {
17
+ if (v === null || v === undefined || typeof v !== "object" || Array.isArray(v)) {
18
+ return undefined;
19
+ }
20
+ return v;
21
+ }
22
+ function readAiTaskProfileFromRecord(container) {
23
+ if (container === undefined)
24
+ return {};
25
+ const raw = container.aiTaskProfile;
26
+ const p = readRecord(raw);
27
+ if (p === undefined)
28
+ return {};
29
+ const webRaw = readRecord(p.webScoping);
30
+ let web;
31
+ if (webRaw !== undefined) {
32
+ const enabled = webRaw.enabled === true;
33
+ const qraw = webRaw.questions;
34
+ const questions = Array.isArray(qraw)
35
+ ? qraw.filter((x) => nonEmptyString(x)).map((s) => s.trim())
36
+ : undefined;
37
+ web = { enabled, ...(questions !== undefined && questions.length > 0 ? { questions } : {}) };
38
+ }
39
+ const synRaw = readRecord(p.inputSynthesis);
40
+ let synthesis;
41
+ if (synRaw !== undefined) {
42
+ const enabled = synRaw.enabled === true;
43
+ const catalogId = nonEmptyString(synRaw.catalogId)
44
+ ? synRaw.catalogId.trim()
45
+ : undefined;
46
+ const strategyKey = nonEmptyString(synRaw.strategyKey)
47
+ ? synRaw.strategyKey.trim()
48
+ : undefined;
49
+ const outputKey = nonEmptyString(synRaw.outputKey)
50
+ ? synRaw.outputKey.trim()
51
+ : undefined;
52
+ const mode = nonEmptyString(synRaw.mode) ? synRaw.mode.trim() : undefined;
53
+ const dest = synRaw.destination;
54
+ const destination = typeof dest === "string" &&
55
+ VALID_SYNTHESIS_DESTINATIONS.has(dest)
56
+ ? dest
57
+ : undefined;
58
+ const sourcesRaw = synRaw.sources;
59
+ const sources = Array.isArray(sourcesRaw)
60
+ ? sourcesRaw.filter((x) => nonEmptyString(x)).map((s) => s.trim())
61
+ : undefined;
62
+ synthesis = {
63
+ enabled,
64
+ ...(catalogId !== undefined ? { catalogId } : {}),
65
+ ...(strategyKey !== undefined ? { strategyKey } : {}),
66
+ ...(outputKey !== undefined ? { outputKey } : {}),
67
+ ...(destination !== undefined ? { destination } : {}),
68
+ ...(sources !== undefined && sources.length > 0 ? { sources } : {}),
69
+ ...(mode !== undefined ? { mode } : {}),
70
+ };
71
+ }
72
+ return {
73
+ pre: nonEmptyString(p.preStrategyKey) ? p.preStrategyKey.trim() : undefined,
74
+ post: nonEmptyString(p.postStrategyKey) ? p.postStrategyKey.trim() : undefined,
75
+ ...(web !== undefined ? { web } : {}),
76
+ ...(synthesis !== undefined ? { synthesis } : {}),
77
+ };
78
+ }
79
+ function hasSynthesisPipelineConflict(node) {
80
+ const tc = readRecord(node.taskConfiguration);
81
+ const profile = readRecord(tc?.aiTaskProfile);
82
+ const syn = readRecord(profile?.inputSynthesis);
83
+ if (syn?.enabled !== true)
84
+ return false;
85
+ const pipeline = node.executionPipeline ?? readRecord(tc)?.executionPipeline;
86
+ if (!Array.isArray(pipeline))
87
+ return false;
88
+ for (const step of pipeline) {
89
+ const s = readRecord(step);
90
+ if (s?.phase === "pre" && s?.type === "synthesized-context")
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+ function localSkillKeySet(utilitySkills, extraLocal) {
96
+ const s = new Set(DEFAULT_LOCAL_SKILL_KEYS);
97
+ for (const u of utilitySkills ?? []) {
98
+ if (typeof u.skillKey === "string" && u.skillKey.trim() !== "") {
99
+ s.add(u.skillKey.trim());
100
+ }
101
+ }
102
+ if (extraLocal !== undefined) {
103
+ for (const k of extraLocal) {
104
+ if (typeof k === "string" && k.trim() !== "")
105
+ s.add(k.trim());
106
+ }
107
+ }
108
+ return s;
109
+ }
110
+ /**
111
+ * Lists **AI task** nodes that violate the **task node execution protocol**
112
+ * (pre/post strategies, web scoping questions, input synthesis strategy).
113
+ */
114
+ export function reportTaskNodeProtocolGaps(graph, options) {
115
+ const localKeys = localSkillKeySet(options?.utilitySkills, options?.extraLocalSkillKeys);
116
+ const g = graph;
117
+ const nodes = g.nodes;
118
+ if (!Array.isArray(nodes))
119
+ return [];
120
+ const out = [];
121
+ for (const n of nodes) {
122
+ if (n === null || typeof n !== "object" || Array.isArray(n))
123
+ continue;
124
+ const node = n;
125
+ if (node.type !== undefined && node.type !== "task")
126
+ continue;
127
+ const id = typeof node.id === "string" ? node.id : "";
128
+ const skillKey = typeof node.skillKey === "string" ? node.skillKey.trim() : "";
129
+ if (id === "" || skillKey === "")
130
+ continue;
131
+ if (localKeys.has(skillKey))
132
+ continue;
133
+ const taskConfiguration = readRecord(node.taskConfiguration);
134
+ const meta = readRecord(node.metadata);
135
+ const fromTaskConfig = readAiTaskProfileFromRecord(taskConfiguration);
136
+ const fromMeta = readAiTaskProfileFromRecord(meta);
137
+ const pre = fromTaskConfig.pre ?? fromMeta.pre;
138
+ const post = fromTaskConfig.post ?? fromMeta.post;
139
+ const web = fromTaskConfig.web ?? fromMeta.web;
140
+ const synthesis = fromTaskConfig.synthesis ?? fromMeta.synthesis;
141
+ const issues = [];
142
+ if (pre === undefined)
143
+ issues.push("missing_preStrategyKey");
144
+ if (post === undefined)
145
+ issues.push("missing_postStrategyKey");
146
+ const wsEnabled = web?.enabled === true;
147
+ const questions = web?.questions ?? [];
148
+ if (wsEnabled && questions.length === 0) {
149
+ issues.push("web_scoping_enabled_without_questions");
150
+ }
151
+ if (!wsEnabled && questions.length > 0) {
152
+ issues.push("web_scoping_questions_without_enabled");
153
+ }
154
+ if (synthesis?.enabled === true) {
155
+ if (!nonEmptyString(synthesis.catalogId)) {
156
+ issues.push("synthesis_enabled_without_catalogId");
157
+ }
158
+ if (!nonEmptyString(synthesis.strategyKey)) {
159
+ issues.push("synthesis_enabled_without_strategyKey");
160
+ }
161
+ if (!nonEmptyString(synthesis.outputKey)) {
162
+ issues.push("synthesis_enabled_without_outputKey");
163
+ }
164
+ if (!synthesis.sources || synthesis.sources.length === 0) {
165
+ issues.push("synthesis_enabled_without_sources");
166
+ }
167
+ if (synthesis.destination === undefined) {
168
+ issues.push("synthesis_enabled_without_destination");
169
+ }
170
+ }
171
+ if (hasSynthesisPipelineConflict(node)) {
172
+ issues.push("synthesis_pipeline_conflict");
173
+ }
174
+ if (issues.length > 0) {
175
+ out.push({ nodeId: id, skillKey, issues });
176
+ }
177
+ }
178
+ return out;
179
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Graph document shell canonicalization (nodes array, response, top-level keys).
3
+ */
4
+ /** Convert record-keyed `nodes` to an array; returns whether conversion happened. */
5
+ export declare function normalizeNodesToArray(graph: Record<string, unknown>): boolean;
6
+ export declare function canonicalizeGraphDocumentShell(graph: Record<string, unknown>): boolean;
7
+ export declare function canonicalizeTaskNodePlacement(node: Record<string, unknown>): boolean;
8
+ //# sourceMappingURL=canonicalGraphDocument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalGraphDocument.d.ts","sourceRoot":"","sources":["../src/canonicalGraphDocument.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmTH,qFAAqF;AACrF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAc7E;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CA8BtF;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAQpF"}
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Graph document shell canonicalization (nodes array, response, top-level keys).
3
+ */
4
+ const CANONICAL_TOP_LEVEL_KEYS = new Set([
5
+ "id",
6
+ "version",
7
+ "modelConfig",
8
+ "jobKnowledge",
9
+ "nodes",
10
+ "edges",
11
+ "variables",
12
+ "response",
13
+ "metadata",
14
+ ]);
15
+ const FORBIDDEN_ROOT_RUNTIME_KEYS = new Set([
16
+ "input",
17
+ "inputs",
18
+ "jobMemory",
19
+ "taskMemory",
20
+ "executionMemory",
21
+ "outputsMemory",
22
+ "aliasConfig",
23
+ "outputConstraints",
24
+ "taskKnowledge",
25
+ ]);
26
+ const FORBIDDEN_GRAPH_RESPONSE_KEYS = [
27
+ "version",
28
+ "target",
29
+ "primaryResponsePaths",
30
+ "debugResponsePaths",
31
+ "notableExecutionPaths",
32
+ "mappingPreset",
33
+ ];
34
+ const FORBIDDEN_NARRIX_WEB_KEYS = [
35
+ "webScopeQuestions",
36
+ "webScoping",
37
+ "webScopeTemplates",
38
+ "forceWebScope",
39
+ "webScopeEntityIdPath",
40
+ ];
41
+ /** Metadata execution keys → taskConfiguration target (value transform optional). */
42
+ const METADATA_EXECUTION_LIFT = {
43
+ aiTaskProfile: { target: "aiTaskProfile" },
44
+ narrix: { target: "narrix" },
45
+ narrixMode: { target: "narrixMode" },
46
+ narrixInput: { target: "narrixInput" },
47
+ synthesizedContext: {
48
+ target: "aiTaskProfile",
49
+ transform: (v) => {
50
+ const syn = readRecord(v);
51
+ if (syn !== undefined)
52
+ return { inputSynthesis: syn };
53
+ if (v === true)
54
+ return { inputSynthesis: { enabled: true } };
55
+ return { inputSynthesis: v };
56
+ },
57
+ },
58
+ executionStrategyKey: {
59
+ target: "executionStrategies",
60
+ transform: (v) => (typeof v === "string" && v.trim() !== "" ? [{ strategyKey: v.trim() }] : v),
61
+ },
62
+ executionStrategies: { target: "executionStrategies" },
63
+ outputConstraints: {
64
+ target: "aiTasksOutputValidation",
65
+ transform: (v) => {
66
+ const o = readRecord(v);
67
+ if (o !== undefined && "schema" in o)
68
+ return o;
69
+ return { schema: v };
70
+ },
71
+ },
72
+ aiTasksOutputValidation: { target: "aiTasksOutputValidation" },
73
+ modelConfig: { target: "modelConfig" },
74
+ llmCall: { target: "llmCall" },
75
+ inputStrategyKey: { target: "inputStrategyKey" },
76
+ synthesizedInput: { target: "synthesizedInput" },
77
+ stepRetryPolicy: { target: "stepRetryPolicy" },
78
+ concurrency: { target: "concurrency" },
79
+ taskKind: { target: "taskKind" },
80
+ autoValidateDecisionOutput: { target: "autoValidateDecisionOutput" },
81
+ identity: { target: "runTaskIdentity" },
82
+ runTaskIdentity: { target: "runTaskIdentity" },
83
+ memoryKey: { target: "memoryKey" },
84
+ taskTypeId: { target: "taskTypeId" },
85
+ scopingMapId: { target: "scopingMapId" },
86
+ entityIdPath: { target: "entityIdPath" },
87
+ rules: { target: "rules" },
88
+ };
89
+ const NODE_ROOT_TO_TASK_CONFIGURATION = [
90
+ "smartInput",
91
+ "executionPipeline",
92
+ "conditions",
93
+ "outputValidation",
94
+ ];
95
+ function readRecord(v) {
96
+ if (v === null || v === undefined || typeof v !== "object" || Array.isArray(v)) {
97
+ return undefined;
98
+ }
99
+ return v;
100
+ }
101
+ function nonEmptyString(v) {
102
+ return typeof v === "string" && v.trim().length > 0;
103
+ }
104
+ function extractWebQuestionsFromNarrix(narrix) {
105
+ const raw = narrix.webScopeQuestions;
106
+ if (!Array.isArray(raw))
107
+ return [];
108
+ const out = [];
109
+ for (const item of raw) {
110
+ if (typeof item === "string" && item.trim() !== "") {
111
+ out.push(item.trim());
112
+ continue;
113
+ }
114
+ const o = readRecord(item);
115
+ const q = o?.question;
116
+ if (typeof q === "string" && q.trim() !== "")
117
+ out.push(q.trim());
118
+ }
119
+ return out;
120
+ }
121
+ function mergeWebScopingFromNarrix(taskConfiguration, narrix) {
122
+ let changed = false;
123
+ const questions = extractWebQuestionsFromNarrix(narrix);
124
+ const enabled = narrix.enableWebScope === true ||
125
+ narrix.forceWebScope === true ||
126
+ questions.length > 0;
127
+ const profile = {
128
+ ...readRecord(taskConfiguration.aiTaskProfile),
129
+ };
130
+ const existingWeb = readRecord(profile.webScoping);
131
+ const existingQuestions = Array.isArray(existingWeb?.questions)
132
+ ? existingWeb.questions.filter((x) => nonEmptyString(x))
133
+ : [];
134
+ if (questions.length > 0 || enabled) {
135
+ const mergedQuestions = [...new Set([...existingQuestions, ...questions])];
136
+ profile.webScoping = {
137
+ enabled: existingWeb?.enabled === true || enabled || mergedQuestions.length > 0,
138
+ ...(mergedQuestions.length > 0 ? { questions: mergedQuestions } : {}),
139
+ };
140
+ taskConfiguration.aiTaskProfile = profile;
141
+ changed = true;
142
+ }
143
+ for (const k of FORBIDDEN_NARRIX_WEB_KEYS) {
144
+ if (k in narrix) {
145
+ delete narrix[k];
146
+ changed = true;
147
+ }
148
+ }
149
+ return changed;
150
+ }
151
+ function stripForbiddenNarrixWebKeys(narrix) {
152
+ let changed = false;
153
+ for (const k of FORBIDDEN_NARRIX_WEB_KEYS) {
154
+ if (k in narrix) {
155
+ delete narrix[k];
156
+ changed = true;
157
+ }
158
+ }
159
+ return changed;
160
+ }
161
+ function liftSkillKeyAndVariables(node) {
162
+ let changed = false;
163
+ const data = readRecord(node.data);
164
+ const meta = readRecord(node.metadata);
165
+ if (data?.skillKey !== undefined && node.skillKey === undefined) {
166
+ node.skillKey = data.skillKey;
167
+ delete data.skillKey;
168
+ changed = true;
169
+ }
170
+ if (meta?.skillKey !== undefined && node.skillKey === undefined) {
171
+ node.skillKey = meta.skillKey;
172
+ delete meta.skillKey;
173
+ changed = true;
174
+ }
175
+ if (data?.variables !== undefined && node.variables === undefined) {
176
+ node.variables = data.variables;
177
+ delete data.variables;
178
+ changed = true;
179
+ }
180
+ if (meta?.variables !== undefined && node.variables === undefined) {
181
+ node.variables = meta.variables;
182
+ delete meta.variables;
183
+ changed = true;
184
+ }
185
+ if (data !== undefined && Object.keys(data).length === 0) {
186
+ delete node.data;
187
+ changed = true;
188
+ }
189
+ if (meta !== undefined && Object.keys(meta).length === 0 && node.metadata !== undefined) {
190
+ // keep metadata if other keys remain
191
+ }
192
+ return changed;
193
+ }
194
+ function liftMetadataExecutionToTaskConfiguration(node) {
195
+ const meta = readRecord(node.metadata);
196
+ if (meta === undefined)
197
+ return false;
198
+ let changed = false;
199
+ const taskConfiguration = {
200
+ ...readRecord(node.taskConfiguration),
201
+ };
202
+ for (const [key, spec] of Object.entries(METADATA_EXECUTION_LIFT)) {
203
+ if (!(key in meta) || meta[key] === undefined)
204
+ continue;
205
+ const raw = meta[key];
206
+ delete meta[key];
207
+ if (spec.target === "aiTaskProfile") {
208
+ const patch = (spec.transform ? spec.transform(raw) : raw);
209
+ const profile = {
210
+ ...readRecord(taskConfiguration.aiTaskProfile),
211
+ ...patch,
212
+ };
213
+ if (readRecord(taskConfiguration.aiTaskProfile)?.inputSynthesis !== undefined && patch.inputSynthesis !== undefined) {
214
+ profile.inputSynthesis = {
215
+ ...readRecord(readRecord(taskConfiguration.aiTaskProfile)?.inputSynthesis),
216
+ ...readRecord(patch.inputSynthesis),
217
+ };
218
+ }
219
+ taskConfiguration.aiTaskProfile = profile;
220
+ changed = true;
221
+ continue;
222
+ }
223
+ if (taskConfiguration[spec.target] === undefined) {
224
+ taskConfiguration[spec.target] = spec.transform ? spec.transform(raw) : raw;
225
+ changed = true;
226
+ }
227
+ }
228
+ if (!changed)
229
+ return false;
230
+ node.taskConfiguration = taskConfiguration;
231
+ if (Object.keys(meta).length === 0) {
232
+ delete node.metadata;
233
+ }
234
+ else {
235
+ node.metadata = meta;
236
+ }
237
+ return true;
238
+ }
239
+ function liftNodeRootToTaskConfiguration(node) {
240
+ let changed = false;
241
+ const taskConfiguration = {
242
+ ...readRecord(node.taskConfiguration),
243
+ };
244
+ for (const key of NODE_ROOT_TO_TASK_CONFIGURATION) {
245
+ if (node[key] !== undefined && taskConfiguration[key] === undefined) {
246
+ taskConfiguration[key] = node[key];
247
+ delete node[key];
248
+ changed = true;
249
+ }
250
+ }
251
+ if (!changed)
252
+ return false;
253
+ node.taskConfiguration = taskConfiguration;
254
+ return true;
255
+ }
256
+ function removeLegacySynthesisFlags(node) {
257
+ const tc = readRecord(node.taskConfiguration);
258
+ const profile = readRecord(tc?.aiTaskProfile);
259
+ const syn = readRecord(profile?.inputSynthesis);
260
+ if (syn === undefined || !("alsoWriteLegacySynthesizedContext" in syn))
261
+ return false;
262
+ const next = { ...syn };
263
+ delete next.alsoWriteLegacySynthesizedContext;
264
+ const nextProfile = { ...profile, inputSynthesis: next };
265
+ node.taskConfiguration = { ...tc, aiTaskProfile: nextProfile };
266
+ return true;
267
+ }
268
+ function canonicalizeNarrixWebOnNode(node) {
269
+ const tc = readRecord(node.taskConfiguration);
270
+ if (tc === undefined)
271
+ return false;
272
+ let changed = false;
273
+ const narrix = readRecord(tc.narrix);
274
+ if (narrix !== undefined && mergeWebScopingFromNarrix(tc, narrix)) {
275
+ changed = true;
276
+ }
277
+ if (narrix !== undefined && stripForbiddenNarrixWebKeys(narrix)) {
278
+ changed = true;
279
+ }
280
+ if (changed)
281
+ node.taskConfiguration = tc;
282
+ return changed;
283
+ }
284
+ /** Convert record-keyed `nodes` to an array; returns whether conversion happened. */
285
+ export function normalizeNodesToArray(graph) {
286
+ const nodes = graph.nodes;
287
+ if (Array.isArray(nodes))
288
+ return false;
289
+ if (nodes === null || typeof nodes !== "object")
290
+ return false;
291
+ const next = [];
292
+ for (const [key, value] of Object.entries(nodes)) {
293
+ const node = readRecord(value);
294
+ if (node === undefined)
295
+ continue;
296
+ if (node.id === undefined)
297
+ node.id = key;
298
+ next.push(node);
299
+ }
300
+ graph.nodes = next;
301
+ return true;
302
+ }
303
+ export function canonicalizeGraphDocumentShell(graph) {
304
+ let changed = false;
305
+ if (normalizeNodesToArray(graph))
306
+ changed = true;
307
+ for (const key of Object.keys(graph)) {
308
+ if (!CANONICAL_TOP_LEVEL_KEYS.has(key)) {
309
+ if (FORBIDDEN_ROOT_RUNTIME_KEYS.has(key)) {
310
+ delete graph[key];
311
+ changed = true;
312
+ }
313
+ }
314
+ }
315
+ const response = readRecord(graph.response);
316
+ if (response !== undefined) {
317
+ let responseChanged = false;
318
+ for (const k of FORBIDDEN_GRAPH_RESPONSE_KEYS) {
319
+ if (k in response) {
320
+ delete response[k];
321
+ responseChanged = true;
322
+ }
323
+ }
324
+ if (responseChanged) {
325
+ graph.response = response;
326
+ changed = true;
327
+ }
328
+ }
329
+ return changed;
330
+ }
331
+ export function canonicalizeTaskNodePlacement(node) {
332
+ let changed = false;
333
+ if (liftSkillKeyAndVariables(node))
334
+ changed = true;
335
+ if (liftMetadataExecutionToTaskConfiguration(node))
336
+ changed = true;
337
+ if (liftNodeRootToTaskConfiguration(node))
338
+ changed = true;
339
+ if (canonicalizeNarrixWebOnNode(node))
340
+ changed = true;
341
+ if (removeLegacySynthesisFlags(node))
342
+ changed = true;
343
+ return changed;
344
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Warn-first canonical inspection (does not throw). Use before enabling
3
+ * `assertCanonicalGraphDocument` hard-fail in a future minor.
4
+ */
5
+ export declare function collectCanonicalGraphWarnings(graph: object): string[];
6
+ //# sourceMappingURL=canonicalGraphWarnings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalGraphWarnings.d.ts","sourceRoot":"","sources":["../src/canonicalGraphWarnings.ts"],"names":[],"mappings":"AAwDA;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsHrE"}
@@ -0,0 +1,140 @@
1
+ import { getCanonicalGraphDocumentViolations, collectAiTasksNodeExtensionIssues, } from "./graphEngineBridge.js";
2
+ function readRecord(v) {
3
+ if (v === null || v === undefined || typeof v !== "object" || Array.isArray(v)) {
4
+ return undefined;
5
+ }
6
+ return v;
7
+ }
8
+ /** Execution keys that must not remain on task `metadata` after canonicalize. */
9
+ const FORBIDDEN_TASK_METADATA_EXECUTION_KEYS = new Set([
10
+ "aiTaskProfile",
11
+ "narrix",
12
+ "narrixMode",
13
+ "narrixInput",
14
+ "synthesizedContext",
15
+ "executionStrategyKey",
16
+ "executionStrategies",
17
+ "outputConstraints",
18
+ "aiTasksOutputValidation",
19
+ "modelConfig",
20
+ "llmCall",
21
+ "inputStrategyKey",
22
+ "synthesizedInput",
23
+ "skillKey",
24
+ "variables",
25
+ "taskKnowledge",
26
+ "jobKnowledge",
27
+ "jobMemory",
28
+ "taskMemory",
29
+ "executionMemory",
30
+ "outputsMemory",
31
+ "aliasConfig",
32
+ ]);
33
+ const FORBIDDEN_NARRIX_WEB_KEYS = [
34
+ "webScopeQuestions",
35
+ "webScoping",
36
+ "webScopeTemplates",
37
+ "forceWebScope",
38
+ "webScopeEntityIdPath",
39
+ ];
40
+ const PURE_TASK_METADATA_KEYS = new Set([
41
+ "graphReadability",
42
+ "catalogBinding",
43
+ "catalogRequest",
44
+ "name",
45
+ "description",
46
+ "exellixContractTarget",
47
+ "__exellixVirtualBoundary",
48
+ ]);
49
+ /**
50
+ * Warn-first canonical inspection (does not throw). Use before enabling
51
+ * `assertCanonicalGraphDocument` hard-fail in a future minor.
52
+ */
53
+ export function collectCanonicalGraphWarnings(graph) {
54
+ const warnings = [];
55
+ const g = graph;
56
+ const topLevel = getCanonicalGraphDocumentViolations(graph);
57
+ if (topLevel.length > 0) {
58
+ warnings.push(`Canonical graph document: forbidden top-level key(s): ${topLevel.join(", ")}.`);
59
+ }
60
+ const nodes = g.nodes;
61
+ if (!Array.isArray(nodes)) {
62
+ if (nodes !== undefined) {
63
+ warnings.push("Canonical graph document: nodes must be an array (record-keyed nodes were removed in 5.x).");
64
+ }
65
+ return warnings;
66
+ }
67
+ for (let i = 0; i < nodes.length; i++) {
68
+ const node = readRecord(nodes[i]);
69
+ if (node === undefined)
70
+ continue;
71
+ const nodeId = typeof node.id === "string" ? node.id : `nodes[${i}]`;
72
+ const prefix = `node "${nodeId}"`;
73
+ const data = readRecord(node.data);
74
+ if (data !== undefined) {
75
+ if ("skillKey" in data) {
76
+ warnings.push(`${prefix}: skillKey must be on node.skillKey, not node.data.skillKey.`);
77
+ }
78
+ if ("variables" in data) {
79
+ warnings.push(`${prefix}: variables must be on node.variables, not node.data.variables.`);
80
+ }
81
+ }
82
+ const meta = readRecord(node.metadata);
83
+ if (meta !== undefined) {
84
+ for (const key of Object.keys(meta)) {
85
+ if (FORBIDDEN_TASK_METADATA_EXECUTION_KEYS.has(key)) {
86
+ warnings.push(`${prefix}: forbidden metadata.${key} — move execution config to taskConfiguration.`);
87
+ }
88
+ else if (!PURE_TASK_METADATA_KEYS.has(key) && node.type !== "finalizer") {
89
+ warnings.push(`${prefix}: metadata.${key} is not in the planning-only allowlist.`);
90
+ }
91
+ }
92
+ }
93
+ if (node.type !== "finalizer" && "outputMapping" in node) {
94
+ warnings.push(`${prefix}: outputMapping belongs on finalizers; use executionMapping for task nodes.`);
95
+ }
96
+ const tc = readRecord(node.taskConfiguration);
97
+ const narrix = readRecord(tc?.narrix);
98
+ if (narrix !== undefined) {
99
+ for (const k of FORBIDDEN_NARRIX_WEB_KEYS) {
100
+ if (k in narrix) {
101
+ warnings.push(`${prefix}: taskConfiguration.narrix.${k} is forbidden — author web scope under taskConfiguration.aiTaskProfile.webScoping.`);
102
+ }
103
+ }
104
+ }
105
+ const profile = readRecord(tc?.aiTaskProfile);
106
+ const inputSynthesis = readRecord(profile?.inputSynthesis);
107
+ if (inputSynthesis !== undefined && "alsoWriteLegacySynthesizedContext" in inputSynthesis) {
108
+ warnings.push(`${prefix}: taskConfiguration.aiTaskProfile.inputSynthesis.alsoWriteLegacySynthesizedContext is removed in 5.x.`);
109
+ }
110
+ if (inputSynthesis?.enabled === true && Array.isArray(node.executionPipeline)) {
111
+ for (const step of node.executionPipeline) {
112
+ const s = readRecord(step);
113
+ if (s?.phase === "pre" && s?.type === "synthesized-context") {
114
+ warnings.push(`${prefix}: inputSynthesis.enabled conflicts with manual PRE synthesized-context in executionPipeline.`);
115
+ break;
116
+ }
117
+ }
118
+ }
119
+ const extIssues = collectAiTasksNodeExtensionIssues(node, `nodes[${i}]`);
120
+ for (const issue of extIssues) {
121
+ warnings.push(`${prefix}: ${issue.code} at ${issue.path}: ${issue.message}`);
122
+ }
123
+ }
124
+ const response = readRecord(g.response);
125
+ if (response !== undefined) {
126
+ const forbiddenResponse = [
127
+ "version",
128
+ "target",
129
+ "primaryResponsePaths",
130
+ "debugResponsePaths",
131
+ "notableExecutionPaths",
132
+ "mappingPreset",
133
+ ];
134
+ const bad = forbiddenResponse.filter((k) => k in response);
135
+ if (bad.length > 0) {
136
+ warnings.push(`graph.response: editor-only key(s) should be removed: ${bad.join(", ")}.`);
137
+ }
138
+ }
139
+ return warnings;
140
+ }