sfscanner 0.1.0 → 0.2.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.
Files changed (53) hide show
  1. package/README.md +141 -214
  2. package/THIRD_PARTY_NOTICES.md +385 -48
  3. package/dist/_types/contracts/preflight-prerequisite-types.d.ts +73 -0
  4. package/dist/_types/packages/scanner-cli/src/index.d.ts +6 -0
  5. package/dist/_types/packages/scanner-cli/src/v2/arguments.d.ts +102 -0
  6. package/dist/_types/packages/scanner-cli/src/v2/cli-runner.d.ts +22 -0
  7. package/dist/{v2 → _types/packages/scanner-cli/src/v2}/commands.d.ts +10 -5
  8. package/dist/{v2 → _types/packages/scanner-cli/src/v2}/contracts.d.ts +53 -9
  9. package/dist/_types/packages/scanner-cli/src/v2/help.d.ts +64 -0
  10. package/dist/_types/packages/scanner-cli/src/v2/metadata-output.d.ts +37 -0
  11. package/dist/_types/packages/scanner-cli/src/v2/preflight-readiness.d.ts +108 -0
  12. package/dist/_types/packages/scanner-cli/src/v2/privacy-prepare.d.ts +39 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +26129 -0
  15. package/dist/index.js.map +7 -0
  16. package/dist/main.js +26874 -12830
  17. package/dist/main.js.map +4 -4
  18. package/dist/mcp.js +19357 -7477
  19. package/dist/mcp.js.map +4 -4
  20. package/dist/scan-worker.js +28702 -0
  21. package/dist/scan-worker.js.map +7 -0
  22. package/dist/v2/arguments.d.ts +1 -86
  23. package/dist/v2/arguments.js +220 -21
  24. package/dist/v2/arguments.js.map +3 -3
  25. package/dist/v2/cli-runner.d.ts +1 -28
  26. package/dist/v2/cli-runner.js +19080 -7387
  27. package/dist/v2/cli-runner.js.map +4 -4
  28. package/dist/v2/help.d.ts +1 -27
  29. package/dist/v2/help.js +77 -9
  30. package/dist/v2/help.js.map +2 -2
  31. package/dist/v2/metadata-output.d.ts +1 -37
  32. package/dist/v2/metadata-output.js +20917 -276
  33. package/dist/v2/metadata-output.js.map +4 -4
  34. package/dist/v2/preflight-readiness.d.ts +1 -0
  35. package/dist/v2/preflight-readiness.js +447 -0
  36. package/dist/v2/preflight-readiness.js.map +7 -0
  37. package/dist/v2/privacy-prepare.d.ts +1 -0
  38. package/dist/v2/privacy-prepare.js +232 -0
  39. package/dist/v2/privacy-prepare.js.map +7 -0
  40. package/package.json +82 -67
  41. package/dist/v2/atomic-output.d.ts +0 -34
  42. package/dist/v2/batch-runner.d.ts +0 -44
  43. package/dist/v2/config.d.ts +0 -16
  44. package/dist/v2/exit-reducer.d.ts +0 -23
  45. package/dist/v2/input-read.d.ts +0 -26
  46. package/dist/v2/input-selection.d.ts +0 -38
  47. package/dist/v2/json-output.d.ts +0 -97
  48. package/dist/v2/metadata-validation.d.ts +0 -4
  49. package/dist/v2/ndjson-output.d.ts +0 -3
  50. package/dist/v2/sarif-helpers.d.ts +0 -69
  51. package/dist/v2/sarif-output.d.ts +0 -40
  52. package/dist/v2/table-output.d.ts +0 -2
  53. package/dist/v2/worker-host.d.ts +0 -24
@@ -0,0 +1 @@
1
+ export * from "../_types/packages/scanner-cli/src/v2/preflight-readiness.js";
@@ -0,0 +1,447 @@
1
+ // ../../contracts/preflight-prerequisites.ts
2
+ var SUBWORKFLOW_TYPE = "n8n-nodes-base.executeWorkflow";
3
+ var OFFICIAL_PREFIXES = Object.freeze(["n8n-nodes-base.", "@n8n/n8n-nodes-langchain."]);
4
+ var FLOW_UTILITY_TYPES = Object.freeze({
5
+ "n8n-nodes-base.set": true,
6
+ "n8n-nodes-base.code": true,
7
+ "n8n-nodes-base.if": true,
8
+ "n8n-nodes-base.switch": true,
9
+ "n8n-nodes-base.filter": true,
10
+ "n8n-nodes-base.merge": true,
11
+ "n8n-nodes-base.splitInBatches": true,
12
+ "n8n-nodes-base.splitOut": true,
13
+ "n8n-nodes-base.aggregate": true,
14
+ "n8n-nodes-base.itemLists": true,
15
+ "n8n-nodes-base.sort": true,
16
+ "n8n-nodes-base.limit": true,
17
+ "n8n-nodes-base.removeDuplicates": true,
18
+ "n8n-nodes-base.wait": true,
19
+ "n8n-nodes-base.noOp": true,
20
+ "n8n-nodes-base.start": true,
21
+ "n8n-nodes-base.stickyNote": true,
22
+ "n8n-nodes-base.dateTime": true,
23
+ "n8n-nodes-base.compareDatasets": true,
24
+ "n8n-nodes-base.stopAndError": true,
25
+ "n8n-nodes-base.respondToWebhook": true,
26
+ "n8n-nodes-base.webhook": true,
27
+ "n8n-nodes-base.formTrigger": true,
28
+ "n8n-nodes-base.scheduleTrigger": true,
29
+ "n8n-nodes-base.manualTrigger": true,
30
+ "n8n-nodes-base.errorTrigger": true,
31
+ "n8n-nodes-base.executeWorkflowTrigger": true,
32
+ "n8n-nodes-base.crypto": true
33
+ });
34
+ var REMOTE_ACCESS_TYPES = Object.freeze({
35
+ "n8n-nodes-base.httpRequest": true,
36
+ "n8n-nodes-base.graphql": true
37
+ });
38
+ var KNOWN_INTEGRATION_TYPES = Object.freeze({ "n8n-nodes-base.slack": true, "n8n-nodes-base.gmail": true, "n8n-nodes-base.postgres": true });
39
+ var MAX_ENVIRONMENT_DEPTH = 64;
40
+ var MAX_ENVIRONMENT_VALUES = 1e4;
41
+ var MAX_ENVIRONMENT_CHARS = 1e6;
42
+ var ENVIRONMENT_REFERENCE = /(\$env|\$vars)\.([A-Za-z0-9_]+)/g;
43
+ var COMPUTED_ENVIRONMENT_REFERENCE = /(\$env|\$vars)\s*(?:\?\.\s*)?\[/;
44
+ var SAFE_PARAMETER_KEYS = /* @__PURE__ */ new Set(["url", "options", "headers", "list", "jsCode", "text", "value", "jsonBody"]);
45
+ var UNKNOWNS_BASE = Object.freeze([
46
+ "credential references are export identifiers only; secrets, scopes, ownership, expiry and offline validity stay unknown.",
47
+ "npm package versions and installation state are not recorded in a workflow export; community node package versions stay unknown.",
48
+ "disabled nodes are listed as exported; whether a disabled node needs its prerequisites at run time is not evaluated.",
49
+ "environment references are literal $env/$vars mentions; the value, the variable's existence and its availability on a target stay unknown."
50
+ ]);
51
+ var PROVENANCE = Object.freeze({
52
+ source: "WORKFLOW_EXPORT",
53
+ pointer: "/",
54
+ repository: "UNKNOWN",
55
+ commit: "UNKNOWN",
56
+ licence: "UNKNOWN",
57
+ verification: "UNKNOWN"
58
+ });
59
+ var ro = (value) => Object.freeze(value);
60
+ var isDict = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
61
+ var cmp = (left, right) => left < right ? -1 : left > right ? 1 : 0;
62
+ var text = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
63
+ var isOfficial = (type) => OFFICIAL_PREFIXES.some((prefix) => type.startsWith(prefix));
64
+ var hasCredentialReference = (node) => node.credentials !== null && Object.values(node.credentials).some((entry) => isDict(entry) && (text(entry.id) !== null || text(entry.name) !== null));
65
+ var hasKnownIdentity = (node) => FLOW_UTILITY_TYPES[node.type] === true || REMOTE_ACCESS_TYPES[node.type] === true || node.type === SUBWORKFLOW_TYPE || KNOWN_INTEGRATION_TYPES[node.type] === true;
66
+ var normalizeTypeVersion = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? String(value) : text(value);
67
+ var escapeJsonPointerSegment = (value) => value.replaceAll("~", "~0").replaceAll("/", "~1");
68
+ var nodeEvidence = (node) => ro({
69
+ source: "WORKFLOW_EXPORT",
70
+ pointer: `/nodes/${node.index}`,
71
+ node: node.name,
72
+ nodeType: node.type,
73
+ typeVersion: node.typeVersion
74
+ });
75
+ function readNodes(workflow) {
76
+ if (!isDict(workflow)) throw new TypeError("workflow must be an object");
77
+ const raw = workflow.nodes;
78
+ if (!Array.isArray(raw)) throw new TypeError("workflow.nodes must be an array");
79
+ const nodes = [];
80
+ let malformed = 0;
81
+ raw.forEach((item, index) => {
82
+ const type = isDict(item) ? text(item.type) : null;
83
+ if (type === null) {
84
+ malformed += 1;
85
+ return;
86
+ }
87
+ nodes.push(ro({ index, name: text(item.name) ?? `node[${index}]`, type, typeVersion: normalizeTypeVersion(item.typeVersion), parameters: item.parameters, credentials: isDict(item.credentials) ? item.credentials : null }));
88
+ });
89
+ return ro({ nodes: ro(nodes), malformed });
90
+ }
91
+ function collectCredentials(nodes) {
92
+ const rows = [];
93
+ for (const node of nodes) {
94
+ const credentials = node.credentials;
95
+ if (credentials === null) continue;
96
+ for (const credentialType of Object.keys(credentials)) {
97
+ const details = isDict(credentials[credentialType]) ? credentials[credentialType] : {};
98
+ const hasId = text(details.id) !== null;
99
+ const hasName = text(details.name) !== null;
100
+ const pointer = `/nodes/${node.index}/credentials/${escapeJsonPointerSegment(credentialType)}`;
101
+ rows.push(ro({ credentialType, credentialName: text(details.name), reference: hasId && hasName ? "ID_AND_NAME" : hasId ? "ID_ONLY" : hasName ? "NAME_ONLY" : "EMPTY", node: node.name, nodeType: node.type, pointer, offlineValidity: "UNKNOWN", ownership: "UNKNOWN" }));
102
+ }
103
+ }
104
+ return ro(rows.sort((left, right) => cmp(left.credentialType, right.credentialType) || cmp(left.node, right.node)));
105
+ }
106
+ var evidenceRows = (evidence) => ro(evidence.slice().sort((left, right) => left.index - right.index).map(({ row }) => row));
107
+ function collectCommunityNodes(nodes) {
108
+ const groups = /* @__PURE__ */ new Map();
109
+ for (const node of nodes) {
110
+ if (isOfficial(node.type)) continue;
111
+ const dot = node.type.lastIndexOf(".");
112
+ const packageName = dot > 0 ? node.type.slice(0, dot) : null;
113
+ const group = groups.get(String(packageName)) ?? { packageName, nodeTypes: [], nodes: [], typeVersions: [], evidence: [] };
114
+ if (!group.nodeTypes.includes(node.type)) group.nodeTypes.push(node.type);
115
+ group.nodes.push(node.name);
116
+ group.evidence.push({ index: node.index, row: nodeEvidence(node) });
117
+ if (node.typeVersion !== null && !group.typeVersions.includes(node.typeVersion)) group.typeVersions.push(node.typeVersion);
118
+ groups.set(String(packageName), group);
119
+ }
120
+ return ro([...groups.values()].map((group) => ro({
121
+ packageName: group.packageName,
122
+ nodeTypes: ro(group.nodeTypes),
123
+ nodes: ro(group.nodes),
124
+ typeVersions: ro(group.typeVersions),
125
+ evidence: evidenceRows(group.evidence),
126
+ packageVersion: null,
127
+ installed: "UNKNOWN"
128
+ })).sort((left, right) => cmp(String(left.packageName), String(right.packageName)) || cmp(left.nodeTypes.join(), right.nodeTypes.join())));
129
+ }
130
+ function collectIntegrations(nodes) {
131
+ const groups = /* @__PURE__ */ new Map();
132
+ for (const node of nodes) {
133
+ if (REMOTE_ACCESS_TYPES[node.type] !== true && !(KNOWN_INTEGRATION_TYPES[node.type] === true && hasCredentialReference(node))) continue;
134
+ const group = groups.get(node.type) ?? { nodes: [], credentialTypes: [], evidence: [] };
135
+ group.nodes.push(node.name);
136
+ group.evidence.push({ index: node.index, row: nodeEvidence(node) });
137
+ for (const key of node.credentials === null ? [] : Object.keys(node.credentials)) if (!group.credentialTypes.includes(key)) group.credentialTypes.push(key);
138
+ groups.set(node.type, group);
139
+ }
140
+ return ro([...groups.entries()].map(([nodeType, group]) => ro({
141
+ service: nodeType.slice(nodeType.lastIndexOf(".") + 1),
142
+ nodeType,
143
+ nodes: ro(group.nodes),
144
+ credentialTypes: ro(group.credentialTypes.sort(cmp)),
145
+ evidence: evidenceRows(group.evidence)
146
+ })).sort((left, right) => cmp(left.nodeType, right.nodeType)));
147
+ }
148
+ function collectSubworkflows(nodes) {
149
+ return ro(nodes.filter((node) => node.type === SUBWORKFLOW_TYPE).map((node) => {
150
+ const parameters = isDict(node.parameters) ? node.parameters : {};
151
+ const hasWorkflowId = Object.prototype.hasOwnProperty.call(parameters, "workflowId");
152
+ const raw = hasWorkflowId ? parameters.workflowId : void 0;
153
+ const literal = typeof raw === "string" ? raw : isDict(raw) ? text(raw.value) : null;
154
+ const dynamic = typeof raw === "string" ? raw.includes("{{") : isDict(raw) && raw.value !== void 0;
155
+ const resolution = literal === null ? "ABSENT" : dynamic ? "DYNAMIC" : "LITERAL";
156
+ const pointer = hasWorkflowId ? `/nodes/${node.index}/parameters/workflowId` : `/nodes/${node.index}`;
157
+ return ro({ node: node.name, pointer, targetWorkflowId: resolution === "LITERAL" ? literal : null, resolution, targetExistence: "UNKNOWN" });
158
+ }).sort((left, right) => cmp(left.node, right.node)));
159
+ }
160
+ function* parameterKeys(value, arrayLimit) {
161
+ if (Array.isArray(value)) {
162
+ for (let index = 0; index < Math.min(value.length, arrayLimit); index += 1) yield String(index);
163
+ } else {
164
+ for (const key in value) yield key;
165
+ }
166
+ }
167
+ function recordEnvironmentReferences(hit, pointer, value, hits, seen, budget) {
168
+ for (const match of value.matchAll(ENVIRONMENT_REFERENCE)) {
169
+ const namespace = match[1] === "$env" ? "ENV" : "VARS";
170
+ const name = match[2];
171
+ const key = `${pointer}\0${namespace}\0${name}`;
172
+ if (seen.has(key)) continue;
173
+ seen.add(key);
174
+ hits.push(ro({ index: hit.index, evidence: hit.evidence, pointer, namespace, name }));
175
+ }
176
+ if (COMPUTED_ENVIRONMENT_REFERENCE.test(value)) budget.computed = true;
177
+ }
178
+ function scanParameters(node, evidence, hits, seen, budget) {
179
+ const stack = [{ value: node.parameters, pointer: `/nodes/${node.index}/parameters`, depth: 0, safe: true }];
180
+ while (stack.length > 0) {
181
+ const entry = stack.pop();
182
+ budget.visited += 1;
183
+ if (budget.visited > MAX_ENVIRONMENT_VALUES || entry.depth > MAX_ENVIRONMENT_DEPTH) {
184
+ budget.truncated = true;
185
+ return;
186
+ }
187
+ if (typeof entry.value === "string") {
188
+ budget.characters += entry.value.length;
189
+ if (budget.characters > MAX_ENVIRONMENT_CHARS) {
190
+ budget.truncated = true;
191
+ return;
192
+ }
193
+ if (entry.safe) recordEnvironmentReferences({ index: node.index, evidence }, entry.pointer, entry.value, hits, seen, budget);
194
+ else {
195
+ if (entry.value.search(ENVIRONMENT_REFERENCE) >= 0) budget.omitted = true;
196
+ if (COMPUTED_ENVIRONMENT_REFERENCE.test(entry.value)) budget.computed = true;
197
+ }
198
+ continue;
199
+ }
200
+ if (!Array.isArray(entry.value) && !isDict(entry.value)) continue;
201
+ const array = Array.isArray(entry.value);
202
+ const arrayLimit = MAX_ENVIRONMENT_VALUES - budget.visited - stack.length + 1;
203
+ for (const key of parameterKeys(entry.value, arrayLimit)) {
204
+ if (!Object.prototype.hasOwnProperty.call(entry.value, key)) continue;
205
+ if (budget.visited + stack.length >= MAX_ENVIRONMENT_VALUES) {
206
+ budget.truncated = true;
207
+ break;
208
+ }
209
+ if (!array) budget.characters += key.length;
210
+ if (budget.characters > MAX_ENVIRONMENT_CHARS) {
211
+ budget.truncated = true;
212
+ return;
213
+ }
214
+ const safe = entry.safe && (array || SAFE_PARAMETER_KEYS.has(key));
215
+ const pointer = safe ? `${entry.pointer}/${escapeJsonPointerSegment(key)}` : entry.pointer;
216
+ const value = array ? entry.value[Number(key)] : entry.value[key];
217
+ stack.push({ value, pointer, depth: entry.depth + 1, safe });
218
+ }
219
+ if (array && entry.value.length > arrayLimit) budget.truncated = true;
220
+ }
221
+ }
222
+ function collectEnvironment(nodes, budget) {
223
+ const hits = [];
224
+ const seen = /* @__PURE__ */ new Set();
225
+ for (const node of nodes) {
226
+ if (budget.truncated || budget.visited >= MAX_ENVIRONMENT_VALUES) {
227
+ budget.truncated = true;
228
+ break;
229
+ }
230
+ scanParameters(node, nodeEvidence(node), hits, seen, budget);
231
+ }
232
+ return ro(hits.map(({ index, evidence, pointer, namespace, name }) => ({ index, pointer, row: ro({
233
+ namespace,
234
+ name,
235
+ value: "UNKNOWN",
236
+ availability: "UNKNOWN",
237
+ evidence: ro({ ...evidence, pointer })
238
+ }) })).sort((left, right) => left.index - right.index || cmp(left.pointer, right.pointer) || cmp(left.row.namespace, right.row.namespace) || cmp(left.row.name, right.row.name)).map(({ row }) => row));
239
+ }
240
+ function collectPreflightInventory(workflow) {
241
+ const { nodes, malformed } = readNodes(workflow);
242
+ const budget = { visited: 0, characters: 0, truncated: false, computed: false, omitted: false };
243
+ const environment = collectEnvironment(nodes, budget);
244
+ const subworkflows = collectSubworkflows(nodes);
245
+ const unknowns = [...UNKNOWNS_BASE];
246
+ if (subworkflows.some((row) => row.resolution === "DYNAMIC")) unknowns.push("a dynamic subworkflow id, and whether any referenced workflow exists, cannot be resolved offline.");
247
+ if (budget.truncated) unknowns.push("parameter traversal stopped at an internal inventory limit, so the environment reference inventory is incomplete.");
248
+ if (budget.computed) unknowns.push("computed environment or variable access ($env[...] or $vars[...]) cannot be named offline and stays unknown.");
249
+ if (budget.omitted) unknowns.push("environment evidence omitted because a parameter path may contain sensitive dynamic keys.");
250
+ return ro({
251
+ nodes,
252
+ malformed,
253
+ inventoryTruncated: budget.truncated,
254
+ environmentIncomplete: ro({ computed: budget.computed, omitted: budget.omitted }),
255
+ prerequisites: ro({
256
+ credentials: collectCredentials(nodes),
257
+ communityNodes: collectCommunityNodes(nodes),
258
+ integrations: collectIntegrations(nodes),
259
+ subworkflows,
260
+ environment,
261
+ provenance: PROVENANCE,
262
+ unknowns: ro(unknowns)
263
+ })
264
+ });
265
+ }
266
+
267
+ // src/v2/preflight-target-facts.ts
268
+ var NODES_CONFIG = "packages/@n8n/config/src/configs/nodes.config.ts";
269
+ var PINNED_TARGET_FACTS = Object.freeze({
270
+ "2.0.0": Object.freeze({
271
+ version: "2.0.0",
272
+ nodeType: "n8n-nodes-base.executeCommand",
273
+ source: "https://raw.githubusercontent.com/n8n-io/n8n/a8ecda44f7627630bc8b78cf671405157ad41c4f/packages/%40n8n/config/src/configs/nodes.config.ts",
274
+ evidence: Object.freeze({
275
+ kind: "PINNED_SOURCE",
276
+ tag: "n8n@2.0.0",
277
+ commit: "a8ecda44f7627630bc8b78cf671405157ad41c4f",
278
+ path: NODES_CONFIG,
279
+ startLine: 29,
280
+ endLine: 36,
281
+ sha256: "88f7169bf5a39d053a8b256a7711afa5f0f66c0f84eeae214497537c6a70ebe8"
282
+ })
283
+ }),
284
+ "2.45.6": Object.freeze({
285
+ version: "2.45.6",
286
+ nodeType: "n8n-nodes-base.executeCommand",
287
+ source: "https://raw.githubusercontent.com/n8n-io/n8n/e1024601bc8e523198804de13d2f8e27eed92896/packages/%40n8n/config/src/configs/nodes.config.ts",
288
+ evidence: Object.freeze({
289
+ kind: "PINNED_SOURCE",
290
+ tag: "n8n@2.45.6",
291
+ commit: "e1024601bc8e523198804de13d2f8e27eed92896",
292
+ path: NODES_CONFIG,
293
+ startLine: 31,
294
+ endLine: 39,
295
+ sha256: "1f57a7f5044d4dc2df9b5880669fa37cc4928e4564c378c8e0aef17db30b5aa3"
296
+ })
297
+ })
298
+ });
299
+ function pinnedTargetFactV1(version) {
300
+ if (version !== "2.0.0" && version !== "2.45.6") return void 0;
301
+ return PINNED_TARGET_FACTS[version];
302
+ }
303
+
304
+ // src/v2/preflight-readiness.ts
305
+ var PREFLIGHT_READINESS_SCHEMA_V1 = "n8n-preflight-readiness-v1";
306
+ var PREFLIGHT_SEVERITIES_V1 = Object.freeze(["critical", "high", "medium", "low", "info"]);
307
+ var PREFLIGHT_DISCLAIMER_V1 = Object.freeze({
308
+ id: "NOT_SAFETY_CLEARANCE",
309
+ text: "A clean or low-severity scan is not safety clearance: it does not prove the workflow is bug-free, secure, fully analyzed or runnable, and it does not replace an import, execution, deployment or credential check on the declared target."
310
+ });
311
+ var DEFAULT_PLATFORM_FACTS_V1 = Object.freeze([]);
312
+ var SEVERITY_RANK = Object.freeze({ critical: 0, high: 1, medium: 2, low: 3, info: 4 });
313
+ var COMPLETENESS_ORDER = Object.freeze(["MALFORMED_NODE_ENTRY", "UNKNOWN_NODE_TYPE", "UNSUPPORTED_NODE", "UNRESOLVED_EDGE", "AMBIGUITY", "TRUNCATED_ANALYSIS", "DYNAMIC_PREREQUISITE_VALUE", "PREREQUISITE_INVENTORY_LIMIT", "DYNAMIC_ENVIRONMENT_ACCESS", "ENVIRONMENT_EVIDENCE_OMITTED", "ANALYSIS_NOT_REPORTED"]);
314
+ var ro2 = (value) => Object.freeze(value);
315
+ var isCounter = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
316
+ function readAnalysis(analysis, nodeCount) {
317
+ const { supportedNodes, unsupportedNodes, unresolvedEdges, ambiguities, truncated } = isDict(analysis) ? analysis : {};
318
+ if (!isCounter(supportedNodes) || supportedNodes > nodeCount || !isCounter(unsupportedNodes) || !isCounter(unresolvedEdges) || !isCounter(ambiguities) || typeof truncated !== "boolean") return ro2({ reported: false, supportedNodes: 0, unsupportedNodes: 0, unresolvedEdges: 0, ambiguities: 0, truncated: false });
319
+ return ro2({ reported: true, supportedNodes, unsupportedNodes, unresolvedEdges, ambiguities, truncated });
320
+ }
321
+ function compareTypeVersions(left, right) {
322
+ if (!/^[0-9]+(\.[0-9]+){0,2}$/.test(left) || !/^[0-9]+(\.[0-9]+){0,2}$/.test(right)) return null;
323
+ const parts = (value) => [...value.split(".").map(Number), 0, 0].slice(0, 3);
324
+ const a = parts(left);
325
+ const b = parts(right);
326
+ return (a[0] ?? 0) - (b[0] ?? 0) || (a[1] ?? 0) - (b[1] ?? 0) || (a[2] ?? 0) - (b[2] ?? 0);
327
+ }
328
+ function completenessReasons(options, inventory) {
329
+ const { malformed, nodes, subworkflows, inventoryTruncated, environmentIncomplete } = inventory;
330
+ const signals = readAnalysis(options.analysis, nodes.length);
331
+ const counts = {
332
+ ANALYSIS_NOT_REPORTED: signals.reported ? 0 : 1,
333
+ MALFORMED_NODE_ENTRY: malformed,
334
+ UNKNOWN_NODE_TYPE: nodes.filter((node) => !hasKnownIdentity(node)).length,
335
+ UNSUPPORTED_NODE: signals.reported ? Math.max(signals.unsupportedNodes, nodes.length - signals.supportedNodes) : 0,
336
+ UNRESOLVED_EDGE: signals.unresolvedEdges,
337
+ AMBIGUITY: signals.ambiguities,
338
+ TRUNCATED_ANALYSIS: signals.truncated ? 1 : 0,
339
+ DYNAMIC_PREREQUISITE_VALUE: subworkflows.filter((row) => row.resolution === "DYNAMIC").length,
340
+ PREREQUISITE_INVENTORY_LIMIT: inventoryTruncated ? 1 : 0,
341
+ DYNAMIC_ENVIRONMENT_ACCESS: environmentIncomplete.computed ? 1 : 0,
342
+ ENVIRONMENT_EVIDENCE_OMITTED: environmentIncomplete.omitted ? 1 : 0
343
+ };
344
+ const details = {
345
+ ANALYSIS_NOT_REPORTED: "Analysis signals are absent, partial or malformed, so the retained evidence cannot prove the workflow was fully analyzed.",
346
+ MALFORMED_NODE_ENTRY: "Node entries without a type were skipped and stay unanalyzed.",
347
+ UNKNOWN_NODE_TYPE: "Node types with no admitted local-utility or remote-access model, and no credential reference declared on an official type, have no built-in semantic model.",
348
+ UNSUPPORTED_NODE: "Nodes whose semantics the analyzer reported as unsupported were not modeled.",
349
+ UNRESOLVED_EDGE: "Connection edges that could not be resolved leave the reachable graph partial.",
350
+ AMBIGUITY: "Duplicate or ambiguous members were reported by normalization and stay unresolved.",
351
+ TRUNCATED_ANALYSIS: "Analysis stopped at a bound, so the retained evidence covers only part of the workflow.",
352
+ DYNAMIC_PREREQUISITE_VALUE: "A subworkflow id is dynamic and cannot be resolved offline.",
353
+ PREREQUISITE_INVENTORY_LIMIT: "Parameter traversal stopped at an internal inventory limit, so the environment reference inventory may be incomplete.",
354
+ DYNAMIC_ENVIRONMENT_ACCESS: "Computed environment or variable access cannot be named offline.",
355
+ ENVIRONMENT_EVIDENCE_OMITTED: "Environment evidence was omitted because its parameter path may contain sensitive dynamic keys."
356
+ };
357
+ return ro2(COMPLETENESS_ORDER.filter((code) => counts[code] > 0).map((code) => ro2({ code, count: counts[code], detail: details[code] })));
358
+ }
359
+ function normalizeFindings(input) {
360
+ if (input === void 0) return ro2([]);
361
+ if (!Array.isArray(input)) throw new TypeError("findings must be an array");
362
+ const rows = input.map((item, index) => {
363
+ const ruleId = isDict(item) ? text(item.ruleId) : null;
364
+ if (ruleId === null) throw new TypeError(`findings[${index}] needs a ruleId`);
365
+ const severity = isDict(item) && typeof item.severity === "string" ? item.severity.trim().toLowerCase() : "";
366
+ if (!PREFLIGHT_SEVERITIES_V1.includes(severity)) throw new TypeError(`findings[${index}] has unsupported severity ${JSON.stringify(isDict(item) ? item.severity : void 0)}`);
367
+ return ro2({ ruleId, severity, node: isDict(item) ? text(item.node) : null, message: isDict(item) ? text(item.message) : null });
368
+ });
369
+ return ro2(rows.sort((left, right) => SEVERITY_RANK[left.severity] - SEVERITY_RANK[right.severity] || cmp(left.ruleId, right.ruleId) || cmp(left.node ?? "", right.node ?? "")));
370
+ }
371
+ var isPlatformFact = (fact) => isDict(fact) && text(fact.featureId) !== null && text(fact.source) !== null && Array.isArray(fact.nodeTypes) && fact.nodeTypes.every((type) => text(type) !== null) && Array.isArray(fact.supportedTargets) && fact.supportedTargets.every((target) => target === "1.x" || target === "2.x") && (fact.maxTypeVersionByTarget === void 0 || isDict(fact.maxTypeVersionByTarget) && Object.entries(fact.maxTypeVersionByTarget).every(([target, version]) => (target === "1.x" || target === "2.x") && text(version) !== null));
372
+ var targetCaveat = "This is not a migration test and does not prove the workflow imports or executes.";
373
+ var observedTypeVersion = (value) => value !== null && /^[0-9]+(\.[0-9]+){0,2}$/.test(value) ? value : null;
374
+ var versionFamily = (version) => version === "2.x" || version.startsWith("2.") ? "2.x" : version === "1.x" || version.startsWith("1.") ? "1.x" : null;
375
+ function evaluateExactTarget(version, nodes) {
376
+ const fact = pinnedTargetFactV1(version);
377
+ const declared = versionFamily(version);
378
+ if (fact === void 0) return ro2({ declared, declaredVersion: version, status: "INDETERMINATE", migrationTested: false, findings: ro2([]), notes: ro2([`No applicable fact exists in this exact pinned subset for ${version}; target status remains indeterminate, with no further conclusion from this packet and no migration-success claim.`]) });
379
+ const matched = nodes.filter((node) => node.type === fact.nodeType).sort((left, right) => left.index - right.index || cmp(left.name, right.name));
380
+ if (matched.length === 0) return ro2({ declared, declaredVersion: version, status: "NO_RECOGNIZED_CHANGE", migrationTested: false, findings: ro2([]), notes: ro2([`No executeCommand match exists in the frozen exact pinned subset for ${fact.evidence.tag}. This is not migration success and does not prove the workflow imports or executes.`]) });
381
+ const findings = matched.map((node) => ro2({
382
+ code: "DEFAULT_NODE_EXCLUSION_CONFIGURATION_UNKNOWN",
383
+ featureId: "execute-command-default-exclusion",
384
+ subject: node.name,
385
+ nodeType: fact.nodeType,
386
+ typeVersion: observedTypeVersion(node.typeVersion),
387
+ source: fact.source,
388
+ detail: `The pinned upstream default for ${fact.evidence.tag} contains ${fact.nodeType} in its default NODES_EXCLUDE list. An operator may override NODES_EXCLUDE, but the actual host list and configuration are unknown and must be reviewed.`,
389
+ pointer: `/nodes/${node.index}/type`,
390
+ sourceEvidence: fact.evidence
391
+ }));
392
+ return ro2({ declared, declaredVersion: version, status: "INDETERMINATE", migrationTested: false, findings: ro2(findings), notes: ro2([`The frozen exact pinned subset records the upstream default exclusion; the actual host NODES_EXCLUDE list and configuration are unknown and must be reviewed. ${targetCaveat}`]) });
393
+ }
394
+ function evaluateTarget(options, nodes) {
395
+ if (typeof options.n8nVersion === "string") return evaluateExactTarget(options.n8nVersion, nodes);
396
+ if (options.requiredPlatformFeatures !== void 0 && !Array.isArray(options.requiredPlatformFeatures)) throw new TypeError("requiredPlatformFeatures must be an array");
397
+ const facts = options.platformFacts ?? DEFAULT_PLATFORM_FACTS_V1;
398
+ for (const fact of facts) if (!isPlatformFact(fact)) throw new TypeError("malformed platform compatibility fact");
399
+ const declared = options.n8nTarget === "1.x" || options.n8nTarget === "2.x" ? options.n8nTarget : null;
400
+ const caveat = "this is not a migration test and does not prove the workflow imports or executes";
401
+ if (declared === null) return ro2({ declared: null, status: "INDETERMINATE", migrationTested: false, findings: ro2([]), notes: ro2([`No n8n target was declared, so platform compatibility is unknown; ${caveat} anywhere.`]) });
402
+ const required = options.requiredPlatformFeatures ?? [];
403
+ const findings = [];
404
+ const admitted = new Set(facts.map((fact) => fact.featureId));
405
+ for (const fact of facts) {
406
+ const matched = nodes.filter((node) => fact.nodeTypes.includes(node.type));
407
+ if (matched.length === 0 && !required.includes(fact.featureId)) continue;
408
+ const subject = matched.map((node) => node.name).join(", ") || `feature:${fact.featureId}`;
409
+ if (!fact.supportedTargets.includes(declared)) findings.push(ro2({ code: "FEATURE_NOT_SUPPORTED_BY_TARGET", featureId: fact.featureId, subject, nodeType: matched[0]?.type ?? null, typeVersion: null, source: fact.source, detail: `${text(fact.description) ?? fact.featureId} is not admitted as supported on n8n ${declared}.` }));
410
+ const maximum = fact.maxTypeVersionByTarget?.[declared];
411
+ if (typeof maximum !== "string") continue;
412
+ for (const node of matched) {
413
+ const verdict = node.typeVersion === null ? null : compareTypeVersions(node.typeVersion, maximum);
414
+ if (verdict !== null && verdict <= 0) continue;
415
+ findings.push(ro2({ code: verdict === null ? "TYPE_VERSION_UNKNOWN" : "TYPE_VERSION_NOT_SUPPORTED_BY_TARGET", featureId: fact.featureId, subject: node.name, nodeType: node.type, typeVersion: node.typeVersion, source: fact.source, detail: verdict === null ? `The ${node.name} typeVersion is absent or unreadable, so target support is unknown.` : `typeVersion ${node.typeVersion} exceeds the maximum admitted for n8n ${declared} (${maximum}).` }));
416
+ }
417
+ }
418
+ for (const featureId of new Set(required)) if (!admitted.has(featureId)) findings.push(ro2({ code: "FEATURE_NOT_SUPPORTED_BY_TARGET", featureId, subject: `feature:${featureId}`, nodeType: null, typeVersion: null, source: "caller:requiredPlatformFeatures", detail: `Required platform feature ${featureId} has no admitted fact.` }));
419
+ const status = findings.some((finding) => finding.code !== "TYPE_VERSION_UNKNOWN") ? "INCOMPATIBLE" : findings.length > 0 ? "UNKNOWN" : "NO_RECOGNIZED_CHANGE";
420
+ const conclusion = status === "INCOMPATIBLE" ? "Admitted pinned facts conflict with the declared target." : status === "UNKNOWN" ? "An admitted fact applies but a needed workflow value is missing, so compatibility stays unknown." : "Result: no recognized change found for the declared target.";
421
+ return ro2({ declared, status, migrationTested: false, findings: ro2(findings.sort((left, right) => cmp(left.featureId, right.featureId) || cmp(left.code, right.code) || cmp(left.subject, right.subject))), notes: ro2([`${conclusion} Target ${declared}; ${caveat} on it.`, "Absence of a recognized conflict is not migration success."]) });
422
+ }
423
+ function buildPreflightReadinessReport(options) {
424
+ if (!isDict(options)) throw new TypeError("options must be an object");
425
+ const { nodes, malformed, prerequisites, inventoryTruncated, environmentIncomplete } = collectPreflightInventory(options.workflow);
426
+ const reasons = completenessReasons(options, { malformed, nodes, subworkflows: prerequisites.subworkflows, inventoryTruncated, environmentIncomplete });
427
+ const status = reasons.length === 0 ? "COMPLETE" : reasons.some((reason) => reason.code === "ANALYSIS_NOT_REPORTED") ? "INDETERMINATE" : "DEGRADED";
428
+ const items = normalizeFindings(options.findings);
429
+ const bySeverity = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
430
+ for (const item of items) bySeverity[item.severity] += 1;
431
+ return ro2({
432
+ schemaVersion: PREFLIGHT_READINESS_SCHEMA_V1,
433
+ safetyClearance: "NOT_PROVIDED",
434
+ disclaimer: PREFLIGHT_DISCLAIMER_V1,
435
+ completeness: ro2({ status, reasons }),
436
+ findings: ro2({ total: items.length, bySeverity: ro2(bySeverity), highestSeverity: items[0]?.severity ?? null, items }),
437
+ prerequisites,
438
+ target: evaluateTarget(options, nodes)
439
+ });
440
+ }
441
+ export {
442
+ DEFAULT_PLATFORM_FACTS_V1,
443
+ PREFLIGHT_DISCLAIMER_V1,
444
+ PREFLIGHT_READINESS_SCHEMA_V1,
445
+ PREFLIGHT_SEVERITIES_V1,
446
+ buildPreflightReadinessReport
447
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../contracts/preflight-prerequisites.ts", "../../src/v2/preflight-target-facts.ts", "../../src/v2/preflight-readiness.ts"],
4
+ "sourcesContent": ["/**\n * Traceable prerequisite inventory: turns one workflow export into credential, community-node, integration,\n * subworkflow and environment observations, each anchored to a JSON pointer inside the exported document.\n * Nothing here executes code, reads the host environment or infers ownership, installation or availability;\n * a literal `$env`/`$vars` mention is an observed reference, never proof the variable exists or is used.\n */\nimport type {\n PreflightCommunityNodeV1, PreflightCredentialV1, PreflightEnvironmentV1, PreflightIntegrationV1,\n PreflightNodeEvidenceV1, PreflightPrerequisitesV1, PreflightProvenanceV1, PreflightSubworkflowV1,\n RawNodeV1,\n} from './preflight-prerequisite-types';\n\ntype Dict = Readonly<Record<string, unknown>>;\n\nconst SUBWORKFLOW_TYPE = 'n8n-nodes-base.executeWorkflow';\nconst OFFICIAL_PREFIXES: readonly string[] = Object.freeze(['n8n-nodes-base.', '@n8n/n8n-nodes-langchain.']);\nconst FLOW_UTILITY_TYPES: Readonly<Record<string, true>> = Object.freeze({\n 'n8n-nodes-base.set': true, 'n8n-nodes-base.code': true, 'n8n-nodes-base.if': true, 'n8n-nodes-base.switch': true, 'n8n-nodes-base.filter': true,\n 'n8n-nodes-base.merge': true, 'n8n-nodes-base.splitInBatches': true, 'n8n-nodes-base.splitOut': true, 'n8n-nodes-base.aggregate': true,\n 'n8n-nodes-base.itemLists': true, 'n8n-nodes-base.sort': true, 'n8n-nodes-base.limit': true, 'n8n-nodes-base.removeDuplicates': true,\n 'n8n-nodes-base.wait': true, 'n8n-nodes-base.noOp': true, 'n8n-nodes-base.start': true, 'n8n-nodes-base.stickyNote': true,\n 'n8n-nodes-base.dateTime': true, 'n8n-nodes-base.compareDatasets': true, 'n8n-nodes-base.stopAndError': true, 'n8n-nodes-base.respondToWebhook': true,\n 'n8n-nodes-base.webhook': true, 'n8n-nodes-base.formTrigger': true, 'n8n-nodes-base.scheduleTrigger': true, 'n8n-nodes-base.manualTrigger': true,\n 'n8n-nodes-base.errorTrigger': true, 'n8n-nodes-base.executeWorkflowTrigger': true, 'n8n-nodes-base.crypto': true,\n});\n/** Node types whose admitted semantic model is a remote call, so they are integrations without needing a credential reference. */\nconst REMOTE_ACCESS_TYPES: Readonly<Record<string, true>> = Object.freeze({\n 'n8n-nodes-base.httpRequest': true, 'n8n-nodes-base.graphql': true,\n});\n/** Admitted official service types; nothing else may enter the integration inventory, however official it looks. */\nconst KNOWN_INTEGRATION_TYPES: Readonly<Record<string, true>> = Object.freeze({ 'n8n-nodes-base.slack': true, 'n8n-nodes-base.gmail': true, 'n8n-nodes-base.postgres': true });\n/** Internal inventory bounds, not a replacement for any scanner budget: they only keep one export's traversal finite. */\nconst MAX_ENVIRONMENT_DEPTH = 64;\nconst MAX_ENVIRONMENT_VALUES = 10_000;\nconst MAX_ENVIRONMENT_CHARS = 1_000_000;\nconst ENVIRONMENT_REFERENCE = /(\\$env|\\$vars)\\.([A-Za-z0-9_]+)/g;\nconst COMPUTED_ENVIRONMENT_REFERENCE = /(\\$env|\\$vars)\\s*(?:\\?\\.\\s*)?\\[/;\n/** Only fixed structural keys are admitted in environment evidence paths; other keys may themselves contain secrets. */\nconst SAFE_PARAMETER_KEYS = new Set(['url', 'options', 'headers', 'list', 'jsCode', 'text', 'value', 'jsonBody']);\nconst UNKNOWNS_BASE: readonly string[] = Object.freeze([\n 'credential references are export identifiers only; secrets, scopes, ownership, expiry and offline validity stay unknown.',\n 'npm package versions and installation state are not recorded in a workflow export; community node package versions stay unknown.',\n 'disabled nodes are listed as exported; whether a disabled node needs its prerequisites at run time is not evaluated.',\n \"environment references are literal $env/$vars mentions; the value, the variable's existence and its availability on a target stay unknown.\",\n]);\nconst PROVENANCE: PreflightProvenanceV1 = Object.freeze({\n source: 'WORKFLOW_EXPORT' as const, pointer: '/' as const,\n repository: 'UNKNOWN' as const, commit: 'UNKNOWN' as const, licence: 'UNKNOWN' as const, verification: 'UNKNOWN' as const,\n});\n\nconst ro = <T>(value: T): Readonly<T> => Object.freeze(value);\nexport const isDict = (value: unknown): value is Dict => typeof value === 'object' && value !== null && !Array.isArray(value);\nexport const cmp = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);\n/** Whitespace-only values prove nothing, so text is admitted only after trimming. */\nexport const text = (value: unknown): string | null => (typeof value === 'string' && value.trim().length > 0 ? value.trim() : null);\nconst isOfficial = (type: string): boolean => OFFICIAL_PREFIXES.some((prefix) => type.startsWith(prefix));\n/** A credential key alone proves nothing: only an entry carrying a non-empty id or name declares a real reference. */\nconst hasCredentialReference = (node: RawNodeV1): boolean => node.credentials !== null && Object.values(node.credentials).some((entry) => isDict(entry) && (text(entry.id) !== null || text(entry.name) !== null));\n/** Identity is known only for types the local model admits; an unmodelled type that merely declares credentials stays unknown. */\nexport const hasKnownIdentity = (node: RawNodeV1): boolean => FLOW_UTILITY_TYPES[node.type] === true || REMOTE_ACCESS_TYPES[node.type] === true\n || node.type === SUBWORKFLOW_TYPE || KNOWN_INTEGRATION_TYPES[node.type] === true;\n\nconst normalizeTypeVersion = (value: unknown): string | null => (typeof value === 'number' && Number.isFinite(value) && value >= 0 ? String(value) : text(value));\n\n/** Escapes one JSON pointer token; `/credentials/a~1b~0c` names the key `a/b~c` and never a path. */\nexport const escapeJsonPointerSegment = (value: string): string => value.replaceAll('~', '~0').replaceAll('/', '~1');\n\nconst nodeEvidence = (node: RawNodeV1): PreflightNodeEvidenceV1 => ro({\n source: 'WORKFLOW_EXPORT' as const, pointer: `/nodes/${node.index}`,\n node: node.name, nodeType: node.type, typeVersion: node.typeVersion,\n});\n\nfunction readNodes(workflow: unknown): Readonly<{ nodes: readonly RawNodeV1[]; malformed: number }> {\n if (!isDict(workflow)) throw new TypeError('workflow must be an object');\n const raw = workflow.nodes;\n if (!Array.isArray(raw)) throw new TypeError('workflow.nodes must be an array');\n const nodes: RawNodeV1[] = []; let malformed = 0;\n raw.forEach((item, index) => {\n const type = isDict(item) ? text(item.type) : null; if (type === null) { malformed += 1; return; }\n nodes.push(ro({ index, name: text(item.name) ?? `node[${index}]`, type, typeVersion: normalizeTypeVersion(item.typeVersion), parameters: item.parameters, credentials: isDict(item.credentials) ? item.credentials : null }));\n });\n return ro({ nodes: ro(nodes), malformed });\n}\n\nfunction collectCredentials(nodes: readonly RawNodeV1[]): readonly PreflightCredentialV1[] {\n const rows: PreflightCredentialV1[] = [];\n for (const node of nodes) {\n const credentials = node.credentials;\n if (credentials === null) continue;\n for (const credentialType of Object.keys(credentials)) {\n const details = isDict(credentials[credentialType]) ? credentials[credentialType] as Dict : {};\n const hasId = text(details.id) !== null; const hasName = text(details.name) !== null;\n const pointer = `/nodes/${node.index}/credentials/${escapeJsonPointerSegment(credentialType)}`;\n rows.push(ro({ credentialType, credentialName: text(details.name), reference: hasId && hasName ? 'ID_AND_NAME' : hasId ? 'ID_ONLY' : hasName ? 'NAME_ONLY' : 'EMPTY', node: node.name, nodeType: node.type, pointer, offlineValidity: 'UNKNOWN', ownership: 'UNKNOWN' }));\n }\n }\n return ro(rows.sort((left, right) => cmp(left.credentialType, right.credentialType) || cmp(left.node, right.node)));\n}\n\ntype IndexedEvidence = Readonly<{ index: number; row: PreflightNodeEvidenceV1 }>;\nconst evidenceRows = (evidence: readonly IndexedEvidence[]): readonly PreflightNodeEvidenceV1[] => ro(evidence.slice().sort((left, right) => left.index - right.index).map(({ row }) => row));\n\nfunction collectCommunityNodes(nodes: readonly RawNodeV1[]): readonly PreflightCommunityNodeV1[] {\n const groups = new Map<string, { packageName: string | null; nodeTypes: string[]; nodes: string[]; typeVersions: string[]; evidence: IndexedEvidence[] }>();\n for (const node of nodes) {\n if (isOfficial(node.type)) continue;\n const dot = node.type.lastIndexOf('.'); const packageName = dot > 0 ? node.type.slice(0, dot) : null;\n const group = groups.get(String(packageName)) ?? { packageName, nodeTypes: [], nodes: [], typeVersions: [], evidence: [] };\n if (!group.nodeTypes.includes(node.type)) group.nodeTypes.push(node.type);\n group.nodes.push(node.name); group.evidence.push({ index: node.index, row: nodeEvidence(node) });\n if (node.typeVersion !== null && !group.typeVersions.includes(node.typeVersion)) group.typeVersions.push(node.typeVersion);\n groups.set(String(packageName), group);\n }\n return ro([...groups.values()].map((group) => ro({\n packageName: group.packageName, nodeTypes: ro(group.nodeTypes), nodes: ro(group.nodes), typeVersions: ro(group.typeVersions),\n evidence: evidenceRows(group.evidence), packageVersion: null, installed: 'UNKNOWN' as const,\n })).sort((left, right) => cmp(String(left.packageName), String(right.packageName)) || cmp(left.nodeTypes.join(), right.nodeTypes.join())));\n}\n\nfunction collectIntegrations(nodes: readonly RawNodeV1[]): readonly PreflightIntegrationV1[] {\n const groups = new Map<string, { nodes: string[]; credentialTypes: string[]; evidence: IndexedEvidence[] }>();\n for (const node of nodes) {\n if (REMOTE_ACCESS_TYPES[node.type] !== true && !(KNOWN_INTEGRATION_TYPES[node.type] === true && hasCredentialReference(node))) continue;\n const group = groups.get(node.type) ?? { nodes: [], credentialTypes: [], evidence: [] };\n group.nodes.push(node.name); group.evidence.push({ index: node.index, row: nodeEvidence(node) });\n for (const key of node.credentials === null ? [] : Object.keys(node.credentials)) if (!group.credentialTypes.includes(key)) group.credentialTypes.push(key);\n groups.set(node.type, group);\n }\n return ro([...groups.entries()].map(([nodeType, group]) => ro({\n service: nodeType.slice(nodeType.lastIndexOf('.') + 1), nodeType, nodes: ro(group.nodes),\n credentialTypes: ro(group.credentialTypes.sort(cmp)), evidence: evidenceRows(group.evidence),\n })).sort((left, right) => cmp(left.nodeType, right.nodeType)));\n}\n\nfunction collectSubworkflows(nodes: readonly RawNodeV1[]): readonly PreflightSubworkflowV1[] {\n return ro(nodes.filter((node) => node.type === SUBWORKFLOW_TYPE).map((node) => {\n const parameters = isDict(node.parameters) ? node.parameters : {};\n const hasWorkflowId = Object.prototype.hasOwnProperty.call(parameters, 'workflowId');\n const raw = hasWorkflowId ? parameters.workflowId : undefined;\n const literal = typeof raw === 'string' ? raw : isDict(raw) ? text(raw.value) : null;\n const dynamic = typeof raw === 'string' ? raw.includes('{{') : isDict(raw) && raw.value !== undefined;\n const resolution = literal === null ? 'ABSENT' as const : dynamic ? 'DYNAMIC' as const : 'LITERAL' as const;\n const pointer = hasWorkflowId ? `/nodes/${node.index}/parameters/workflowId` : `/nodes/${node.index}`;\n return ro({ node: node.name, pointer, targetWorkflowId: resolution === 'LITERAL' ? literal : null, resolution, targetExistence: 'UNKNOWN' as const });\n }).sort((left, right) => cmp(left.node, right.node)));\n}\n\ntype EnvironmentBudget = { visited: number; characters: number; truncated: boolean; computed: boolean; omitted: boolean };\ntype EnvironmentHit = Readonly<{ index: number; evidence: PreflightNodeEvidenceV1; pointer: string; namespace: 'ENV' | 'VARS'; name: string }>;\n\nfunction* parameterKeys(value: Dict | readonly unknown[], arrayLimit: number): Generator<string> {\n if (Array.isArray(value)) {\n for (let index = 0; index < Math.min(value.length, arrayLimit); index += 1) yield String(index);\n } else {\n for (const key in value) yield key;\n }\n}\n\n/** Only identifier access is an observation; computed access (`$env[...]`) stays an unnamed unknown. */\nfunction recordEnvironmentReferences(hit: { index: number; evidence: PreflightNodeEvidenceV1 }, pointer: string, value: string, hits: EnvironmentHit[], seen: Set<string>, budget: EnvironmentBudget): void {\n for (const match of value.matchAll(ENVIRONMENT_REFERENCE)) {\n const namespace = match[1] === '$env' ? 'ENV' as const : 'VARS' as const;\n const name = match[2]!; const key = `${pointer}\\u0000${namespace}\\u0000${name}`;\n if (seen.has(key)) continue; seen.add(key);\n hits.push(ro({ index: hit.index, evidence: hit.evidence, pointer, namespace, name }));\n }\n if (COMPUTED_ENVIRONMENT_REFERENCE.test(value)) budget.computed = true;\n}\n\n/** Iterative so a deep or cyclic export cannot overflow the stack, and bounded so one export cannot stall it. */\nfunction scanParameters(node: RawNodeV1, evidence: PreflightNodeEvidenceV1, hits: EnvironmentHit[], seen: Set<string>, budget: EnvironmentBudget): void {\n const stack: Array<{ value: unknown; pointer: string; depth: number; safe: boolean }> = [{ value: node.parameters, pointer: `/nodes/${node.index}/parameters`, depth: 0, safe: true }];\n while (stack.length > 0) {\n const entry = stack.pop()!;\n budget.visited += 1;\n if (budget.visited > MAX_ENVIRONMENT_VALUES || entry.depth > MAX_ENVIRONMENT_DEPTH) { budget.truncated = true; return; }\n if (typeof entry.value === 'string') {\n budget.characters += entry.value.length;\n if (budget.characters > MAX_ENVIRONMENT_CHARS) { budget.truncated = true; return; }\n if (entry.safe) recordEnvironmentReferences({ index: node.index, evidence }, entry.pointer, entry.value, hits, seen, budget);\n else {\n if (entry.value.search(ENVIRONMENT_REFERENCE) >= 0) budget.omitted = true;\n if (COMPUTED_ENVIRONMENT_REFERENCE.test(entry.value)) budget.computed = true;\n }\n continue;\n }\n if (!Array.isArray(entry.value) && !isDict(entry.value)) continue;\n const array = Array.isArray(entry.value);\n const arrayLimit = MAX_ENVIRONMENT_VALUES - budget.visited - stack.length + 1;\n for (const key of parameterKeys(entry.value, arrayLimit)) {\n if (!Object.prototype.hasOwnProperty.call(entry.value, key)) continue;\n if (budget.visited + stack.length >= MAX_ENVIRONMENT_VALUES) { budget.truncated = true; break; }\n if (!array) budget.characters += key.length;\n if (budget.characters > MAX_ENVIRONMENT_CHARS) { budget.truncated = true; return; }\n const safe = entry.safe && (array || SAFE_PARAMETER_KEYS.has(key));\n const pointer = safe ? `${entry.pointer}/${escapeJsonPointerSegment(key)}` : entry.pointer;\n const value = array ? (entry.value as unknown[])[Number(key)] : (entry.value as Dict)[key];\n stack.push({ value, pointer, depth: entry.depth + 1, safe });\n }\n if (array && (entry.value as unknown[]).length > arrayLimit) budget.truncated = true;\n }\n}\n\nfunction collectEnvironment(nodes: readonly RawNodeV1[], budget: EnvironmentBudget): readonly PreflightEnvironmentV1[] {\n const hits: EnvironmentHit[] = []; const seen = new Set<string>();\n for (const node of nodes) {\n if (budget.truncated || budget.visited >= MAX_ENVIRONMENT_VALUES) { budget.truncated = true; break; }\n scanParameters(node, nodeEvidence(node), hits, seen, budget);\n }\n return ro(hits.map(({ index, evidence, pointer, namespace, name }) => ({ index, pointer, row: ro({\n namespace, name, value: 'UNKNOWN' as const, availability: 'UNKNOWN' as const, evidence: ro({ ...evidence, pointer }),\n }) }))\n .sort((left, right) => left.index - right.index || cmp(left.pointer, right.pointer) || cmp(left.row.namespace, right.row.namespace) || cmp(left.row.name, right.row.name))\n .map(({ row }) => row));\n}\n\n/** The one entry point: a parsed export, its prerequisite inventory and whether an internal bound cut it short. */\nexport function collectPreflightInventory(workflow: unknown): Readonly<{\n nodes: readonly RawNodeV1[]; malformed: number; prerequisites: PreflightPrerequisitesV1; inventoryTruncated: boolean;\n environmentIncomplete: Readonly<{ computed: boolean; omitted: boolean }>;\n}> {\n const { nodes, malformed } = readNodes(workflow);\n const budget: EnvironmentBudget = { visited: 0, characters: 0, truncated: false, computed: false, omitted: false };\n const environment = collectEnvironment(nodes, budget);\n const subworkflows = collectSubworkflows(nodes);\n const unknowns = [...UNKNOWNS_BASE];\n if (subworkflows.some((row) => row.resolution === 'DYNAMIC')) unknowns.push('a dynamic subworkflow id, and whether any referenced workflow exists, cannot be resolved offline.');\n if (budget.truncated) unknowns.push('parameter traversal stopped at an internal inventory limit, so the environment reference inventory is incomplete.');\n if (budget.computed) unknowns.push('computed environment or variable access ($env[...] or $vars[...]) cannot be named offline and stays unknown.');\n if (budget.omitted) unknowns.push('environment evidence omitted because a parameter path may contain sensitive dynamic keys.');\n return ro({\n nodes, malformed, inventoryTruncated: budget.truncated, environmentIncomplete: ro({ computed: budget.computed, omitted: budget.omitted }),\n prerequisites: ro({\n credentials: collectCredentials(nodes), communityNodes: collectCommunityNodes(nodes), integrations: collectIntegrations(nodes),\n subworkflows, environment, provenance: PROVENANCE, unknowns: ro(unknowns),\n }),\n });\n}\n", "export type PinnedTargetSourceV1 = Readonly<{\n kind: 'PINNED_SOURCE';\n tag: 'n8n@2.0.0' | 'n8n@2.45.6';\n commit: string;\n path: string;\n startLine: number;\n endLine: number;\n sha256: string;\n}>;\n\nexport type PinnedTargetFactV1 = Readonly<{\n version: '2.0.0' | '2.45.6';\n nodeType: 'n8n-nodes-base.executeCommand';\n source: string;\n evidence: PinnedTargetSourceV1;\n}>;\n\nconst NODES_CONFIG = 'packages/@n8n/config/src/configs/nodes.config.ts';\nconst PINNED_TARGET_FACTS: Readonly<Record<'2.0.0' | '2.45.6', PinnedTargetFactV1>> = Object.freeze({\n '2.0.0': Object.freeze({\n version: '2.0.0' as const,\n nodeType: 'n8n-nodes-base.executeCommand' as const,\n source: 'https://raw.githubusercontent.com/n8n-io/n8n/a8ecda44f7627630bc8b78cf671405157ad41c4f/packages/%40n8n/config/src/configs/nodes.config.ts',\n evidence: Object.freeze({\n kind: 'PINNED_SOURCE' as const,\n tag: 'n8n@2.0.0' as const,\n commit: 'a8ecda44f7627630bc8b78cf671405157ad41c4f',\n path: NODES_CONFIG,\n startLine: 29,\n endLine: 36,\n sha256: '88f7169bf5a39d053a8b256a7711afa5f0f66c0f84eeae214497537c6a70ebe8',\n }),\n }),\n '2.45.6': Object.freeze({\n version: '2.45.6' as const,\n nodeType: 'n8n-nodes-base.executeCommand' as const,\n source: 'https://raw.githubusercontent.com/n8n-io/n8n/e1024601bc8e523198804de13d2f8e27eed92896/packages/%40n8n/config/src/configs/nodes.config.ts',\n evidence: Object.freeze({\n kind: 'PINNED_SOURCE' as const,\n tag: 'n8n@2.45.6' as const,\n commit: 'e1024601bc8e523198804de13d2f8e27eed92896',\n path: NODES_CONFIG,\n startLine: 31,\n endLine: 39,\n sha256: '1f57a7f5044d4dc2df9b5880669fa37cc4928e4564c378c8e0aef17db30b5aa3',\n }),\n }),\n});\n\nexport function pinnedTargetFactV1(version: string): PinnedTargetFactV1 | undefined {\n if (version !== '2.0.0' && version !== '2.45.6') return undefined;\n return PINNED_TARGET_FACTS[version];\n}\n", "/**\n * Preflight readiness (PD-01, PD-02): keeps matched finding severity separate from analysis\n * completeness and target-specific compatibility, and inventories the prerequisites a workflow\n * export declares. It never infers credential ownership/validity, package installation, endpoint\n * configuration or live behavior; a no-match result is never safety or migration clearance.\n * Pure, deterministic and free of host APIs so the browser worker and CLI share one contract.\n */\nimport { cmp, collectPreflightInventory, hasKnownIdentity, isDict, text } from '@hub/contracts/preflight-prerequisites';\nimport type {\n PreflightCommunityNodeV1, PreflightCredentialV1, PreflightEnvironmentV1, PreflightIntegrationV1,\n PreflightNodeEvidenceV1, PreflightPrerequisitesV1, PreflightProvenanceV1, PreflightSubworkflowV1,\n RawNodeV1,\n} from '@hub/contracts/preflight-prerequisite-types';\nimport { pinnedTargetFactV1 } from './preflight-target-facts';\n\nexport type {\n PreflightCommunityNodeV1, PreflightCredentialV1, PreflightEnvironmentV1, PreflightIntegrationV1,\n PreflightNodeEvidenceV1, PreflightPrerequisitesV1, PreflightProvenanceV1, PreflightSubworkflowV1,\n} from '@hub/contracts/preflight-prerequisite-types';\n\nexport const PREFLIGHT_READINESS_SCHEMA_V1 = 'n8n-preflight-readiness-v1' as const;\nexport const PREFLIGHT_SEVERITIES_V1 = Object.freeze(['critical', 'high', 'medium', 'low', 'info'] as const);\nexport type PreflightSeverityV1 = (typeof PREFLIGHT_SEVERITIES_V1)[number];\nexport type PreflightCompletenessV1 = 'COMPLETE' | 'DEGRADED' | 'INDETERMINATE';\nexport type N8nTargetV1 = '1.x' | '2.x';\nexport type PreflightCompletenessCodeV1 = 'ANALYSIS_NOT_REPORTED' | 'MALFORMED_NODE_ENTRY' | 'UNKNOWN_NODE_TYPE' | 'UNSUPPORTED_NODE'\n | 'UNRESOLVED_EDGE' | 'AMBIGUITY' | 'TRUNCATED_ANALYSIS' | 'DYNAMIC_PREREQUISITE_VALUE' | 'PREREQUISITE_INVENTORY_LIMIT'\n | 'DYNAMIC_ENVIRONMENT_ACCESS' | 'ENVIRONMENT_EVIDENCE_OMITTED';\nexport type PreflightTargetCodeV1 = 'FEATURE_NOT_SUPPORTED_BY_TARGET' | 'TYPE_VERSION_NOT_SUPPORTED_BY_TARGET' | 'TYPE_VERSION_UNKNOWN'\n | 'DEFAULT_NODE_EXCLUSION_CONFIGURATION_UNKNOWN';\n\nexport const PREFLIGHT_DISCLAIMER_V1 = Object.freeze({\n id: 'NOT_SAFETY_CLEARANCE' as const,\n text: 'A clean or low-severity scan is not safety clearance: it does not prove the workflow is bug-free, secure, fully analyzed or runnable, and it does not replace an import, execution, deployment or credential check on the declared target.',\n});\n\nexport type PreflightFindingInputV1 = Readonly<{ ruleId: string; severity: string; node?: string | null; message?: string | null }>;\nexport type PreflightFindingV1 = Readonly<{ ruleId: string; severity: PreflightSeverityV1; node: string | null; message: string | null }>;\nexport type PreflightAnalysisSignalsV1 = Readonly<{ supportedNodes?: number; unsupportedNodes?: number; unresolvedEdges?: number; ambiguities?: number; truncated?: boolean }>;\nexport type PreflightCompletenessReasonV1 = Readonly<{ code: PreflightCompletenessCodeV1; count: number; detail: string }>;\nexport type PlatformCompatibilityFactV1 = Readonly<{\n featureId: string; description: string; nodeTypes: readonly string[]; supportedTargets: readonly N8nTargetV1[];\n maxTypeVersionByTarget?: Readonly<Partial<Record<N8nTargetV1, string>>>; source: string;\n}>;\nexport type PreflightTargetSourceEvidenceV1 = Readonly<{\n kind: 'PINNED_SOURCE'; tag: string; commit: string; path: string; startLine: number; endLine: number; sha256: string;\n}>;\nexport type PreflightTargetFindingV1 = Readonly<{ code: PreflightTargetCodeV1; featureId: string; subject: string; nodeType: string | null; typeVersion: string | null; source: string; detail: string; pointer?: string; sourceEvidence?: PreflightTargetSourceEvidenceV1 }>;\nexport type PreflightReadinessOptionsV1 = Readonly<{\n workflow: unknown; n8nTarget?: N8nTargetV1 | 'UNKNOWN' | null; platformFacts?: readonly PlatformCompatibilityFactV1[];\n requiredPlatformFeatures?: readonly string[]; analysis?: PreflightAnalysisSignalsV1; findings?: readonly PreflightFindingInputV1[];\n n8nVersion?: string | null;\n}>;\nexport type PreflightReadinessReportV1 = Readonly<{\n schemaVersion: typeof PREFLIGHT_READINESS_SCHEMA_V1; safetyClearance: 'NOT_PROVIDED'; disclaimer: typeof PREFLIGHT_DISCLAIMER_V1;\n completeness: Readonly<{ status: PreflightCompletenessV1; reasons: readonly PreflightCompletenessReasonV1[] }>;\n findings: Readonly<{ total: number; bySeverity: Readonly<Record<PreflightSeverityV1, number>>; highestSeverity: PreflightSeverityV1 | null; items: readonly PreflightFindingV1[] }>;\n prerequisites: PreflightPrerequisitesV1;\n target: Readonly<{ declared: N8nTargetV1 | null; declaredVersion?: string | null; status: 'INDETERMINATE' | 'NO_RECOGNIZED_CHANGE' | 'INCOMPATIBLE' | 'UNKNOWN'; migrationTested: false; findings: readonly PreflightTargetFindingV1[]; notes: readonly string[] }>;\n}>;\n\n/** No pinned platform fact is admitted yet, so every compatibility fact must arrive with its own source (B3). */\nexport const DEFAULT_PLATFORM_FACTS_V1: readonly PlatformCompatibilityFactV1[] = Object.freeze([]);\n\nconst SEVERITY_RANK: Readonly<Record<PreflightSeverityV1, number>> = Object.freeze({ critical: 0, high: 1, medium: 2, low: 3, info: 4 });\nconst COMPLETENESS_ORDER: readonly PreflightCompletenessCodeV1[] = Object.freeze(['MALFORMED_NODE_ENTRY', 'UNKNOWN_NODE_TYPE', 'UNSUPPORTED_NODE', 'UNRESOLVED_EDGE', 'AMBIGUITY', 'TRUNCATED_ANALYSIS', 'DYNAMIC_PREREQUISITE_VALUE', 'PREREQUISITE_INVENTORY_LIMIT', 'DYNAMIC_ENVIRONMENT_ACCESS', 'ENVIRONMENT_EVIDENCE_OMITTED', 'ANALYSIS_NOT_REPORTED']);\nconst ro = <T>(value: T): Readonly<T> => Object.freeze(value);\nconst isCounter = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;\ntype AnalysisEvidenceV1 = Readonly<{ reported: boolean; supportedNodes: number; unsupportedNodes: number; unresolvedEdges: number; ambiguities: number; truncated: boolean }>;\n\n/** Absent, partial or malformed signals prove nothing, so they are never admitted as affirmative analysis evidence. */\nfunction readAnalysis(analysis: unknown, nodeCount: number): AnalysisEvidenceV1 {\n const { supportedNodes, unsupportedNodes, unresolvedEdges, ambiguities, truncated } = isDict(analysis) ? analysis : {};\n if (!isCounter(supportedNodes) || supportedNodes > nodeCount || !isCounter(unsupportedNodes) || !isCounter(unresolvedEdges)\n || !isCounter(ambiguities) || typeof truncated !== 'boolean') return ro({ reported: false, supportedNodes: 0, unsupportedNodes: 0, unresolvedEdges: 0, ambiguities: 0, truncated: false });\n return ro({ reported: true, supportedNodes, unsupportedNodes, unresolvedEdges, ambiguities, truncated });\n}\n\nfunction compareTypeVersions(left: string, right: string): number | null {\n if (!/^[0-9]+(\\.[0-9]+){0,2}$/.test(left) || !/^[0-9]+(\\.[0-9]+){0,2}$/.test(right)) return null;\n const parts = (value: string): number[] => [...value.split('.').map(Number), 0, 0].slice(0, 3); const a = parts(left); const b = parts(right);\n return (a[0] ?? 0) - (b[0] ?? 0) || (a[1] ?? 0) - (b[1] ?? 0) || (a[2] ?? 0) - (b[2] ?? 0);\n}\n\nfunction completenessReasons(options: PreflightReadinessOptionsV1, inventory: Readonly<{\n malformed: number; nodes: readonly RawNodeV1[]; subworkflows: readonly PreflightSubworkflowV1[]; inventoryTruncated: boolean;\n environmentIncomplete: Readonly<{ computed: boolean; omitted: boolean }>;\n}>): readonly PreflightCompletenessReasonV1[] {\n const { malformed, nodes, subworkflows, inventoryTruncated, environmentIncomplete } = inventory;\n const signals = readAnalysis(options.analysis, nodes.length);\n const counts: Record<PreflightCompletenessCodeV1, number> = {\n ANALYSIS_NOT_REPORTED: signals.reported ? 0 : 1, MALFORMED_NODE_ENTRY: malformed,\n UNKNOWN_NODE_TYPE: nodes.filter((node) => !hasKnownIdentity(node)).length, UNSUPPORTED_NODE: signals.reported ? Math.max(signals.unsupportedNodes, nodes.length - signals.supportedNodes) : 0,\n UNRESOLVED_EDGE: signals.unresolvedEdges, AMBIGUITY: signals.ambiguities, TRUNCATED_ANALYSIS: signals.truncated ? 1 : 0,\n DYNAMIC_PREREQUISITE_VALUE: subworkflows.filter((row) => row.resolution === 'DYNAMIC').length, PREREQUISITE_INVENTORY_LIMIT: inventoryTruncated ? 1 : 0,\n DYNAMIC_ENVIRONMENT_ACCESS: environmentIncomplete.computed ? 1 : 0, ENVIRONMENT_EVIDENCE_OMITTED: environmentIncomplete.omitted ? 1 : 0,\n };\n const details: Record<PreflightCompletenessCodeV1, string> = {\n ANALYSIS_NOT_REPORTED: 'Analysis signals are absent, partial or malformed, so the retained evidence cannot prove the workflow was fully analyzed.', MALFORMED_NODE_ENTRY: 'Node entries without a type were skipped and stay unanalyzed.',\n UNKNOWN_NODE_TYPE: 'Node types with no admitted local-utility or remote-access model, and no credential reference declared on an official type, have no built-in semantic model.', UNSUPPORTED_NODE: 'Nodes whose semantics the analyzer reported as unsupported were not modeled.',\n UNRESOLVED_EDGE: 'Connection edges that could not be resolved leave the reachable graph partial.', AMBIGUITY: 'Duplicate or ambiguous members were reported by normalization and stay unresolved.',\n TRUNCATED_ANALYSIS: 'Analysis stopped at a bound, so the retained evidence covers only part of the workflow.', DYNAMIC_PREREQUISITE_VALUE: 'A subworkflow id is dynamic and cannot be resolved offline.',\n PREREQUISITE_INVENTORY_LIMIT: 'Parameter traversal stopped at an internal inventory limit, so the environment reference inventory may be incomplete.',\n DYNAMIC_ENVIRONMENT_ACCESS: 'Computed environment or variable access cannot be named offline.',\n ENVIRONMENT_EVIDENCE_OMITTED: 'Environment evidence was omitted because its parameter path may contain sensitive dynamic keys.',\n };\n return ro(COMPLETENESS_ORDER.filter((code) => counts[code] > 0).map((code) => ro({ code, count: counts[code], detail: details[code] })));\n}\n\nfunction normalizeFindings(input: readonly PreflightFindingInputV1[] | undefined): readonly PreflightFindingV1[] {\n if (input === undefined) return ro([]); if (!Array.isArray(input)) throw new TypeError('findings must be an array');\n const rows = input.map((item, index) => {\n const ruleId = isDict(item) ? text(item.ruleId) : null;\n if (ruleId === null) throw new TypeError(`findings[${index}] needs a ruleId`);\n const severity = isDict(item) && typeof item.severity === 'string' ? item.severity.trim().toLowerCase() : '';\n if (!(PREFLIGHT_SEVERITIES_V1 as readonly string[]).includes(severity)) throw new TypeError(`findings[${index}] has unsupported severity ${JSON.stringify(isDict(item) ? item.severity : undefined)}`);\n return ro({ ruleId, severity: severity as PreflightSeverityV1, node: isDict(item) ? text(item.node) : null, message: isDict(item) ? text(item.message) : null });\n });\n return ro(rows.sort((left, right) => SEVERITY_RANK[left.severity] - SEVERITY_RANK[right.severity] || cmp(left.ruleId, right.ruleId) || cmp(left.node ?? '', right.node ?? '')));\n}\n\n/** A fact is admitted only when every field it carries is well formed; a discarded constraint must not become a clean verdict. */\nconst isPlatformFact = (fact: unknown): fact is PlatformCompatibilityFactV1 => isDict(fact) && text(fact.featureId) !== null && text(fact.source) !== null\n && Array.isArray(fact.nodeTypes) && fact.nodeTypes.every((type) => text(type) !== null) && Array.isArray(fact.supportedTargets) && fact.supportedTargets.every((target) => target === '1.x' || target === '2.x')\n && (fact.maxTypeVersionByTarget === undefined || (isDict(fact.maxTypeVersionByTarget) && Object.entries(fact.maxTypeVersionByTarget).every(([target, version]) => (target === '1.x' || target === '2.x') && text(version) !== null)));\n\nconst targetCaveat = 'This is not a migration test and does not prove the workflow imports or executes.';\nconst observedTypeVersion = (value: string | null): string | null => value !== null && /^[0-9]+(\\.[0-9]+){0,2}$/.test(value) ? value : null;\nconst versionFamily = (version: string): N8nTargetV1 | null => version === '2.x' || version.startsWith('2.') ? '2.x' : version === '1.x' || version.startsWith('1.') ? '1.x' : null;\n\nfunction evaluateExactTarget(version: string, nodes: readonly RawNodeV1[]): PreflightReadinessReportV1['target'] {\n const fact = pinnedTargetFactV1(version);\n const declared = versionFamily(version);\n if (fact === undefined) return ro({ declared, declaredVersion: version, status: 'INDETERMINATE' as const, migrationTested: false as const, findings: ro([]), notes: ro([`No applicable fact exists in this exact pinned subset for ${version}; target status remains indeterminate, with no further conclusion from this packet and no migration-success claim.`]) });\n const matched = nodes.filter((node) => node.type === fact.nodeType).sort((left, right) => left.index - right.index || cmp(left.name, right.name));\n if (matched.length === 0) return ro({ declared, declaredVersion: version, status: 'NO_RECOGNIZED_CHANGE' as const, migrationTested: false as const, findings: ro([]), notes: ro([`No executeCommand match exists in the frozen exact pinned subset for ${fact.evidence.tag}. This is not migration success and does not prove the workflow imports or executes.`]) });\n const findings = matched.map((node) => ro({\n code: 'DEFAULT_NODE_EXCLUSION_CONFIGURATION_UNKNOWN' as const, featureId: 'execute-command-default-exclusion', subject: node.name,\n nodeType: fact.nodeType, typeVersion: observedTypeVersion(node.typeVersion), source: fact.source,\n detail: `The pinned upstream default for ${fact.evidence.tag} contains ${fact.nodeType} in its default NODES_EXCLUDE list. An operator may override NODES_EXCLUDE, but the actual host list and configuration are unknown and must be reviewed.`,\n pointer: `/nodes/${node.index}/type`, sourceEvidence: fact.evidence,\n }));\n return ro({ declared, declaredVersion: version, status: 'INDETERMINATE' as const, migrationTested: false as const, findings: ro(findings), notes: ro([`The frozen exact pinned subset records the upstream default exclusion; the actual host NODES_EXCLUDE list and configuration are unknown and must be reviewed. ${targetCaveat}`]) });\n}\n\nfunction evaluateTarget(options: PreflightReadinessOptionsV1, nodes: readonly RawNodeV1[]): PreflightReadinessReportV1['target'] {\n if (typeof options.n8nVersion === 'string') return evaluateExactTarget(options.n8nVersion, nodes);\n if (options.requiredPlatformFeatures !== undefined && !Array.isArray(options.requiredPlatformFeatures)) throw new TypeError('requiredPlatformFeatures must be an array');\n const facts = options.platformFacts ?? DEFAULT_PLATFORM_FACTS_V1;\n for (const fact of facts) if (!isPlatformFact(fact)) throw new TypeError('malformed platform compatibility fact');\n const declared = options.n8nTarget === '1.x' || options.n8nTarget === '2.x' ? options.n8nTarget : null;\n const caveat = 'this is not a migration test and does not prove the workflow imports or executes';\n if (declared === null) return ro({ declared: null, status: 'INDETERMINATE' as const, migrationTested: false as const, findings: ro([]), notes: ro([`No n8n target was declared, so platform compatibility is unknown; ${caveat} anywhere.`]) });\n const required = options.requiredPlatformFeatures ?? []; const findings: PreflightTargetFindingV1[] = []; const admitted = new Set(facts.map((fact) => fact.featureId));\n for (const fact of facts) {\n const matched = nodes.filter((node) => fact.nodeTypes.includes(node.type));\n if (matched.length === 0 && !required.includes(fact.featureId)) continue;\n const subject = matched.map((node) => node.name).join(', ') || `feature:${fact.featureId}`;\n if (!fact.supportedTargets.includes(declared)) findings.push(ro({ code: 'FEATURE_NOT_SUPPORTED_BY_TARGET' as const, featureId: fact.featureId, subject, nodeType: matched[0]?.type ?? null, typeVersion: null, source: fact.source, detail: `${text(fact.description) ?? fact.featureId} is not admitted as supported on n8n ${declared}.` }));\n const maximum = fact.maxTypeVersionByTarget?.[declared]; if (typeof maximum !== 'string') continue;\n for (const node of matched) {\n const verdict = node.typeVersion === null ? null : compareTypeVersions(node.typeVersion, maximum); if (verdict !== null && verdict <= 0) continue;\n findings.push(ro({ code: verdict === null ? 'TYPE_VERSION_UNKNOWN' as const : 'TYPE_VERSION_NOT_SUPPORTED_BY_TARGET' as const, featureId: fact.featureId, subject: node.name, nodeType: node.type, typeVersion: node.typeVersion, source: fact.source, detail: verdict === null ? `The ${node.name} typeVersion is absent or unreadable, so target support is unknown.` : `typeVersion ${node.typeVersion} exceeds the maximum admitted for n8n ${declared} (${maximum}).` }));\n }\n }\n for (const featureId of new Set(required)) if (!admitted.has(featureId)) findings.push(ro({ code: 'FEATURE_NOT_SUPPORTED_BY_TARGET' as const, featureId, subject: `feature:${featureId}`, nodeType: null, typeVersion: null, source: 'caller:requiredPlatformFeatures', detail: `Required platform feature ${featureId} has no admitted fact.` }));\n const status = findings.some((finding) => finding.code !== 'TYPE_VERSION_UNKNOWN') ? 'INCOMPATIBLE' as const : findings.length > 0 ? 'UNKNOWN' as const : 'NO_RECOGNIZED_CHANGE' as const;\n const conclusion = status === 'INCOMPATIBLE' ? 'Admitted pinned facts conflict with the declared target.'\n : status === 'UNKNOWN' ? 'An admitted fact applies but a needed workflow value is missing, so compatibility stays unknown.'\n : 'Result: no recognized change found for the declared target.';\n return ro({ declared, status, migrationTested: false as const, findings: ro(findings.sort((left, right) => cmp(left.featureId, right.featureId) || cmp(left.code, right.code) || cmp(left.subject, right.subject))), notes: ro([`${conclusion} Target ${declared}; ${caveat} on it.`, 'Absence of a recognized conflict is not migration success.']) });\n}\n\n/** Builds the offline readiness report; the input workflow is never mutated and nothing is executed. */\nexport function buildPreflightReadinessReport(options: PreflightReadinessOptionsV1): PreflightReadinessReportV1 {\n if (!isDict(options)) throw new TypeError('options must be an object');\n const { nodes, malformed, prerequisites, inventoryTruncated, environmentIncomplete } = collectPreflightInventory(options.workflow);\n const reasons = completenessReasons(options, { malformed, nodes, subworkflows: prerequisites.subworkflows, inventoryTruncated, environmentIncomplete });\n const status: PreflightCompletenessV1 = reasons.length === 0 ? 'COMPLETE' : reasons.some((reason) => reason.code === 'ANALYSIS_NOT_REPORTED') ? 'INDETERMINATE' : 'DEGRADED';\n const items = normalizeFindings(options.findings);\n const bySeverity = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };\n for (const item of items) bySeverity[item.severity] += 1;\n return ro({\n schemaVersion: PREFLIGHT_READINESS_SCHEMA_V1, safetyClearance: 'NOT_PROVIDED' as const, disclaimer: PREFLIGHT_DISCLAIMER_V1,\n completeness: ro({ status, reasons }),\n findings: ro({ total: items.length, bySeverity: ro(bySeverity), highestSeverity: items[0]?.severity ?? null, items }),\n prerequisites,\n target: evaluateTarget(options, nodes),\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["ro"]
7
+ }
@@ -0,0 +1 @@
1
+ export * from "../_types/packages/scanner-cli/src/v2/privacy-prepare.js";