@tiangong-lca/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +368 -0
  2. package/bin/tiangong.d.ts +1 -0
  3. package/bin/tiangong.js +32 -0
  4. package/dist/scripts/assert-full-coverage.js +77 -0
  5. package/dist/scripts/assert-full-coverage.js.map +1 -0
  6. package/dist/src/cli.js +2740 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/lib/artifacts.js +86 -0
  9. package/dist/src/lib/artifacts.js.map +1 -0
  10. package/dist/src/lib/dotenv.js +36 -0
  11. package/dist/src/lib/dotenv.js.map +1 -0
  12. package/dist/src/lib/env.js +139 -0
  13. package/dist/src/lib/env.js.map +1 -0
  14. package/dist/src/lib/errors.js +38 -0
  15. package/dist/src/lib/errors.js.map +1 -0
  16. package/dist/src/lib/flow-build-alias-map.js +299 -0
  17. package/dist/src/lib/flow-build-alias-map.js.map +1 -0
  18. package/dist/src/lib/flow-get.js +81 -0
  19. package/dist/src/lib/flow-get.js.map +1 -0
  20. package/dist/src/lib/flow-governance.js +164 -0
  21. package/dist/src/lib/flow-governance.js.map +1 -0
  22. package/dist/src/lib/flow-list.js +187 -0
  23. package/dist/src/lib/flow-list.js.map +1 -0
  24. package/dist/src/lib/flow-publish-reviewed-data.js +808 -0
  25. package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -0
  26. package/dist/src/lib/flow-publish-version.js +541 -0
  27. package/dist/src/lib/flow-publish-version.js.map +1 -0
  28. package/dist/src/lib/flow-read.js +310 -0
  29. package/dist/src/lib/flow-read.js.map +1 -0
  30. package/dist/src/lib/flow-regen-product.js +1396 -0
  31. package/dist/src/lib/flow-regen-product.js.map +1 -0
  32. package/dist/src/lib/flow-remediate.js +821 -0
  33. package/dist/src/lib/flow-remediate.js.map +1 -0
  34. package/dist/src/lib/http.js +45 -0
  35. package/dist/src/lib/http.js.map +1 -0
  36. package/dist/src/lib/io.js +30 -0
  37. package/dist/src/lib/io.js.map +1 -0
  38. package/dist/src/lib/kb-search.js +83 -0
  39. package/dist/src/lib/kb-search.js.map +1 -0
  40. package/dist/src/lib/lifecyclemodel-auto-build.js +1328 -0
  41. package/dist/src/lib/lifecyclemodel-auto-build.js.map +1 -0
  42. package/dist/src/lib/lifecyclemodel-orchestrate.js +1556 -0
  43. package/dist/src/lib/lifecyclemodel-orchestrate.js.map +1 -0
  44. package/dist/src/lib/lifecyclemodel-publish-build.js +296 -0
  45. package/dist/src/lib/lifecyclemodel-publish-build.js.map +1 -0
  46. package/dist/src/lib/lifecyclemodel-publish-resulting-process.js +101 -0
  47. package/dist/src/lib/lifecyclemodel-publish-resulting-process.js.map +1 -0
  48. package/dist/src/lib/lifecyclemodel-resulting-process.js +1152 -0
  49. package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -0
  50. package/dist/src/lib/lifecyclemodel-validate-build.js +238 -0
  51. package/dist/src/lib/lifecyclemodel-validate-build.js.map +1 -0
  52. package/dist/src/lib/llm.js +383 -0
  53. package/dist/src/lib/llm.js.map +1 -0
  54. package/dist/src/lib/process-auto-build.js +717 -0
  55. package/dist/src/lib/process-auto-build.js.map +1 -0
  56. package/dist/src/lib/process-batch-build.js +409 -0
  57. package/dist/src/lib/process-batch-build.js.map +1 -0
  58. package/dist/src/lib/process-get.js +66 -0
  59. package/dist/src/lib/process-get.js.map +1 -0
  60. package/dist/src/lib/process-publish-build.js +451 -0
  61. package/dist/src/lib/process-publish-build.js.map +1 -0
  62. package/dist/src/lib/process-resume-build.js +364 -0
  63. package/dist/src/lib/process-resume-build.js.map +1 -0
  64. package/dist/src/lib/publish.js +562 -0
  65. package/dist/src/lib/publish.js.map +1 -0
  66. package/dist/src/lib/remote.js +88 -0
  67. package/dist/src/lib/remote.js.map +1 -0
  68. package/dist/src/lib/review-flow.js +1110 -0
  69. package/dist/src/lib/review-flow.js.map +1 -0
  70. package/dist/src/lib/review-lifecyclemodel.js +727 -0
  71. package/dist/src/lib/review-lifecyclemodel.js.map +1 -0
  72. package/dist/src/lib/review-process.js +702 -0
  73. package/dist/src/lib/review-process.js.map +1 -0
  74. package/dist/src/lib/run.js +106 -0
  75. package/dist/src/lib/run.js.map +1 -0
  76. package/dist/src/lib/state-lock.js +150 -0
  77. package/dist/src/lib/state-lock.js.map +1 -0
  78. package/dist/src/lib/supabase-client.js +269 -0
  79. package/dist/src/lib/supabase-client.js.map +1 -0
  80. package/dist/src/lib/supabase-json-ordered-write.js +186 -0
  81. package/dist/src/lib/supabase-json-ordered-write.js.map +1 -0
  82. package/dist/src/lib/supabase-rest.js +138 -0
  83. package/dist/src/lib/supabase-rest.js.map +1 -0
  84. package/dist/src/lib/supabase-session.js +424 -0
  85. package/dist/src/lib/supabase-session.js.map +1 -0
  86. package/dist/src/lib/tidas-sdk-package-validator.js +493 -0
  87. package/dist/src/lib/tidas-sdk-package-validator.js.map +1 -0
  88. package/dist/src/lib/unstructured.js +203 -0
  89. package/dist/src/lib/unstructured.js.map +1 -0
  90. package/dist/src/lib/user-api-key.js +75 -0
  91. package/dist/src/lib/user-api-key.js.map +1 -0
  92. package/dist/src/lib/validation.js +191 -0
  93. package/dist/src/lib/validation.js.map +1 -0
  94. package/dist/src/main.js +38 -0
  95. package/dist/src/main.js.map +1 -0
  96. package/package.json +81 -0
@@ -0,0 +1,1556 @@
1
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { readJsonArtifact, writeJsonArtifact } from './artifacts.js';
4
+ import { CliError } from './errors.js';
5
+ import { readJsonInput } from './io.js';
6
+ import { runLifecyclemodelAutoBuild, } from './lifecyclemodel-auto-build.js';
7
+ import { runLifecyclemodelBuildResultingProcess, } from './lifecyclemodel-resulting-process.js';
8
+ import { runProcessAutoBuild } from './process-auto-build.js';
9
+ function isRecord(value) {
10
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
11
+ }
12
+ function copyJson(value) {
13
+ return JSON.parse(JSON.stringify(value));
14
+ }
15
+ function ensureList(value) {
16
+ if (value === undefined || value === null) {
17
+ return [];
18
+ }
19
+ return Array.isArray(value) ? value : [value];
20
+ }
21
+ function nonEmptyString(value) {
22
+ if (typeof value !== 'string') {
23
+ return null;
24
+ }
25
+ const normalized = value.trim();
26
+ return normalized || null;
27
+ }
28
+ function firstNonEmpty(...values) {
29
+ for (const value of values) {
30
+ const normalized = nonEmptyString(value);
31
+ if (normalized) {
32
+ return normalized;
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ function invariant(value, message, details) {
38
+ if (value === undefined || value === null) {
39
+ throw new CliError(message, {
40
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INTERNAL_STATE',
41
+ exitCode: 1,
42
+ details,
43
+ });
44
+ }
45
+ return value;
46
+ }
47
+ function safeSlug(value) {
48
+ const slug = value
49
+ .toLowerCase()
50
+ .replace(/[^a-z0-9]+/gu, '-')
51
+ .replace(/^-+|-+$/gu, '');
52
+ return slug || 'item';
53
+ }
54
+ function nowIso(now = new Date()) {
55
+ return now.toISOString();
56
+ }
57
+ function requireObject(value, label) {
58
+ if (!isRecord(value)) {
59
+ throw new CliError(`${label} must be a JSON object.`, {
60
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
61
+ exitCode: 2,
62
+ details: { label },
63
+ });
64
+ }
65
+ return value;
66
+ }
67
+ function requireKnownKeys(value, allowed, label) {
68
+ const unsupported = Object.keys(value)
69
+ .filter((key) => !allowed.includes(key))
70
+ .sort();
71
+ if (unsupported.length === 0) {
72
+ return;
73
+ }
74
+ throw new CliError(`${label} contains unsupported field(s): ${unsupported.join(', ')}.`, {
75
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
76
+ exitCode: 2,
77
+ details: {
78
+ label,
79
+ unsupported_fields: unsupported,
80
+ allowed_fields: [...allowed],
81
+ },
82
+ });
83
+ }
84
+ function requireEnum(value, allowed, label) {
85
+ const normalized = nonEmptyString(value);
86
+ if (!normalized || !allowed.includes(normalized)) {
87
+ throw new CliError(`${label} must be one of ${allowed.join(', ')}.`, {
88
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
89
+ exitCode: 2,
90
+ details: { label, value, allowed },
91
+ });
92
+ }
93
+ return normalized;
94
+ }
95
+ function requireBoolean(value, label) {
96
+ if (typeof value !== 'boolean') {
97
+ throw new CliError(`${label} must be a boolean.`, {
98
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
99
+ exitCode: 2,
100
+ details: { label, value },
101
+ });
102
+ }
103
+ return value;
104
+ }
105
+ function requireInteger(value, label) {
106
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
107
+ throw new CliError(`${label} must be a non-negative integer.`, {
108
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
109
+ exitCode: 2,
110
+ details: { label, value },
111
+ });
112
+ }
113
+ return value;
114
+ }
115
+ function resolveInputPath(baseDir, raw) {
116
+ const normalized = nonEmptyString(raw);
117
+ if (!normalized) {
118
+ return null;
119
+ }
120
+ if (normalized.includes('://') || normalized.startsWith('file:')) {
121
+ return normalized;
122
+ }
123
+ return path.resolve(baseDir, normalized);
124
+ }
125
+ function defaultCandidateSources() {
126
+ return {
127
+ my_processes: true,
128
+ team_processes: true,
129
+ public_processes: true,
130
+ existing_lifecyclemodels: true,
131
+ existing_resulting_processes: true,
132
+ };
133
+ }
134
+ function entityFromRoot(root) {
135
+ const kind = root.kind;
136
+ if (kind === 'reference_flow') {
137
+ return isRecord(root.flow) ? copyJson(root.flow) : {};
138
+ }
139
+ if (kind === 'process') {
140
+ return isRecord(root.process) ? copyJson(root.process) : {};
141
+ }
142
+ if (kind === 'lifecyclemodel') {
143
+ return isRecord(root.lifecyclemodel) ? copyJson(root.lifecyclemodel) : {};
144
+ }
145
+ if (kind === 'resulting_process') {
146
+ return isRecord(root.resulting_process) ? copyJson(root.resulting_process) : {};
147
+ }
148
+ return {};
149
+ }
150
+ function normalizePublishConfig(publish) {
151
+ return {
152
+ intent: requireEnum(publish.intent, ['dry_run', 'prepare_only', 'publish'], 'publish.intent'),
153
+ prepare_lifecyclemodel_payload: publish.prepare_lifecyclemodel_payload !== false,
154
+ prepare_resulting_process_payload: publish.prepare_resulting_process_payload !== false,
155
+ prepare_relation_payload: publish.prepare_relation_payload !== false,
156
+ };
157
+ }
158
+ function serializeInvocationConfig(value, message, details) {
159
+ return copyJson(invariant(value, message, details));
160
+ }
161
+ function normalizeInvocationFailure(error) {
162
+ return {
163
+ exit_code: error instanceof CliError ? error.exitCode : 1,
164
+ error: error instanceof Error ? error.message : String(error),
165
+ };
166
+ }
167
+ function normalizeCandidate(raw) {
168
+ if (typeof raw === 'string') {
169
+ return { id: raw, score: 1 };
170
+ }
171
+ if (!isRecord(raw)) {
172
+ throw new CliError('candidate must be an object or string.', {
173
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
174
+ exitCode: 2,
175
+ details: { value: raw },
176
+ });
177
+ }
178
+ const id = nonEmptyString(raw.id);
179
+ if (!id) {
180
+ throw new CliError('candidate.id is required.', {
181
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
182
+ exitCode: 2,
183
+ details: { candidate: raw },
184
+ });
185
+ }
186
+ const score = typeof raw.score === 'number' && Number.isFinite(raw.score) && raw.score >= 0 ? raw.score : 1;
187
+ return {
188
+ ...copyJson(raw),
189
+ id,
190
+ score,
191
+ };
192
+ }
193
+ function normalizeCandidateList(value) {
194
+ return ensureList(value)
195
+ .map((entry) => normalizeCandidate(entry))
196
+ .sort((left, right) => right.score - left.score);
197
+ }
198
+ function normalizeRequestedAction(value) {
199
+ return requireEnum(value ?? 'auto', [
200
+ 'auto',
201
+ 'reuse_existing_resulting_process',
202
+ 'reuse_existing_process',
203
+ 'reuse_existing_model',
204
+ 'build_process',
205
+ 'build_submodel',
206
+ 'cutoff',
207
+ 'unresolved',
208
+ ], 'requested_action');
209
+ }
210
+ function normalizeDependsOn(value) {
211
+ return ensureList(value)
212
+ .map((entry) => nonEmptyString(entry))
213
+ .filter((entry) => Boolean(entry));
214
+ }
215
+ function normalizeProcessBuilderConfig(value, baseDir) {
216
+ if (!isRecord(value)) {
217
+ return undefined;
218
+ }
219
+ requireKnownKeys(value, ['flow_file', 'flow_json', 'run_id', 'publish', 'commit', 'forward_args'], 'process_builder');
220
+ return {
221
+ flow_file: resolveInputPath(baseDir, value.flow_file),
222
+ flow_json: value.flow_json,
223
+ run_id: nonEmptyString(value.run_id),
224
+ publish: Boolean(value.publish),
225
+ commit: Boolean(value.commit),
226
+ forward_args: ensureList(value.forward_args)
227
+ .map((entry) => nonEmptyString(entry))
228
+ .filter((entry) => Boolean(entry)),
229
+ };
230
+ }
231
+ function normalizeSubmodelBuilderConfig(value, baseDir) {
232
+ if (!isRecord(value)) {
233
+ return undefined;
234
+ }
235
+ return {
236
+ manifest: resolveInputPath(baseDir, value.manifest),
237
+ out_dir: resolveInputPath(baseDir, value.out_dir),
238
+ dry_run: Boolean(value.dry_run),
239
+ };
240
+ }
241
+ function normalizeProjectorConfig(value, baseDir) {
242
+ if (!isRecord(value)) {
243
+ return undefined;
244
+ }
245
+ return {
246
+ command: requireEnum(value.command ?? 'project', ['prepare', 'build', 'project'], 'projector.command'),
247
+ request: resolveInputPath(baseDir, value.request),
248
+ model_file: resolveInputPath(baseDir, value.model_file),
249
+ out_dir: resolveInputPath(baseDir, value.out_dir),
250
+ projection_role: requireEnum(value.projection_role ?? 'primary', ['primary', 'all'], 'projector.projection_role'),
251
+ run_always: Boolean(value.run_always),
252
+ publish_processes: Boolean(value.publish_processes),
253
+ publish_relations: Boolean(value.publish_relations),
254
+ };
255
+ }
256
+ function normalizeNode(rawNode, index, baseDir) {
257
+ const nodeId = invariant(firstNonEmpty(rawNode.node_id, rawNode.id, `node-${index}`), `Node ${index} id normalization failed unexpectedly.`);
258
+ const kind = requireEnum(rawNode.kind ?? 'process', ['reference_flow', 'process', 'lifecyclemodel', 'resulting_process', 'subsystem'], 'node.kind');
259
+ let entity = isRecord(rawNode.entity) ? copyJson(rawNode.entity) : {};
260
+ if (Object.keys(entity).length === 0) {
261
+ for (const key of ['flow', 'process', 'lifecyclemodel', 'resulting_process']) {
262
+ if (isRecord(rawNode[key])) {
263
+ entity = copyJson(rawNode[key]);
264
+ break;
265
+ }
266
+ }
267
+ }
268
+ const label = invariant(firstNonEmpty(rawNode.label, entity.name, nodeId), `Node ${nodeId} label normalization failed unexpectedly.`);
269
+ const parentNodeId = firstNonEmpty(rawNode.parent_node_id);
270
+ const dependsOn = normalizeDependsOn(rawNode.depends_on);
271
+ if (parentNodeId) {
272
+ dependsOn.push(parentNodeId);
273
+ }
274
+ return {
275
+ node_id: nodeId,
276
+ kind,
277
+ label,
278
+ entity,
279
+ requested_action: normalizeRequestedAction(rawNode.requested_action ?? 'auto'),
280
+ depends_on: [...new Set(dependsOn)].sort(),
281
+ parent_node_id: parentNodeId,
282
+ existing_resulting_process_candidates: normalizeCandidateList(rawNode.existing_resulting_process_candidates),
283
+ existing_process_candidates: normalizeCandidateList(rawNode.existing_process_candidates),
284
+ existing_lifecyclemodel_candidates: normalizeCandidateList(rawNode.existing_lifecyclemodel_candidates),
285
+ process_builder: normalizeProcessBuilderConfig(rawNode.process_builder, baseDir),
286
+ submodel_builder: normalizeSubmodelBuilderConfig(rawNode.submodel_builder, baseDir),
287
+ projector: normalizeProjectorConfig(rawNode.projector, baseDir),
288
+ planned_invocations: [],
289
+ };
290
+ }
291
+ function deriveRootNode(root, goal, baseDir) {
292
+ const entity = entityFromRoot(root);
293
+ const label = invariant(firstNonEmpty(entity.name, goal.name, root.kind, 'root'), 'Root label normalization failed unexpectedly.');
294
+ const rootNode = {
295
+ node_id: invariant(firstNonEmpty(root.node_id, entity.id, 'root'), 'Root node_id normalization failed unexpectedly.'),
296
+ kind: root.kind,
297
+ label,
298
+ entity,
299
+ requested_action: root.requested_action ?? 'auto',
300
+ depends_on: root.depends_on ?? [],
301
+ parent_node_id: root.parent_node_id,
302
+ existing_resulting_process_candidates: root.existing_resulting_process_candidates ?? [],
303
+ existing_process_candidates: root.existing_process_candidates ?? [],
304
+ existing_lifecyclemodel_candidates: root.existing_lifecyclemodel_candidates ?? [],
305
+ process_builder: root.process_builder,
306
+ submodel_builder: root.submodel_builder,
307
+ projector: root.projector,
308
+ };
309
+ return normalizeNode(rootNode, 0, baseDir);
310
+ }
311
+ function buildEdges(requestEdges, nodes) {
312
+ const edges = [];
313
+ const seen = new Set();
314
+ ensureList(requestEdges).forEach((raw) => {
315
+ if (!isRecord(raw)) {
316
+ return;
317
+ }
318
+ const from = nonEmptyString(raw.from);
319
+ const to = nonEmptyString(raw.to);
320
+ const relation = invariant(firstNonEmpty(raw.relation, 'depends_on'), 'Edge relation normalization failed unexpectedly.');
321
+ if (!from || !to) {
322
+ return;
323
+ }
324
+ const key = `${from}::${to}::${relation}`;
325
+ if (seen.has(key)) {
326
+ return;
327
+ }
328
+ seen.add(key);
329
+ edges.push({ from, to, relation });
330
+ });
331
+ nodes.forEach((node) => {
332
+ node.depends_on.forEach((dependency) => {
333
+ const key = `${node.node_id}::${dependency}::depends_on`;
334
+ if (seen.has(key)) {
335
+ return;
336
+ }
337
+ seen.add(key);
338
+ edges.push({
339
+ from: node.node_id,
340
+ to: dependency,
341
+ relation: 'depends_on',
342
+ });
343
+ });
344
+ });
345
+ return edges;
346
+ }
347
+ function topoSortNodes(nodes) {
348
+ const warnings = [];
349
+ const order = new Map(nodes.map((node, index) => [node.node_id, index]));
350
+ const nodeMap = new Map(nodes.map((node) => [node.node_id, node]));
351
+ const indegree = new Map(nodes.map((node) => [node.node_id, 0]));
352
+ const adjacency = new Map(nodes.map((node) => [node.node_id, []]));
353
+ nodes.forEach((node) => {
354
+ node.depends_on.forEach((dependency) => {
355
+ if (!nodeMap.has(dependency)) {
356
+ warnings.push(`Node ${node.node_id} depends on unknown node ${dependency}; keeping it as metadata only.`);
357
+ return;
358
+ }
359
+ indegree.set(node.node_id, invariant(indegree.get(node.node_id), `Missing indegree entry for ${node.node_id}.`, {
360
+ node_id: node.node_id,
361
+ dependency,
362
+ }) + 1);
363
+ invariant(adjacency.get(dependency), `Missing adjacency list for ${dependency}.`, {
364
+ node_id: node.node_id,
365
+ dependency,
366
+ }).push(node.node_id);
367
+ });
368
+ });
369
+ const queue = nodes
370
+ .filter((node) => invariant(indegree.get(node.node_id), `Missing indegree entry for ${node.node_id}.`) === 0)
371
+ .sort((left, right) => invariant(order.get(left.node_id), `Missing order index for ${left.node_id}.`) -
372
+ invariant(order.get(right.node_id), `Missing order index for ${right.node_id}.`))
373
+ .map((node) => node.node_id);
374
+ const ordered = [];
375
+ while (queue.length > 0) {
376
+ const nodeId = invariant(queue.shift(), 'Queue unexpectedly emptied during topological sort.');
377
+ ordered.push(invariant(nodeMap.get(nodeId), `Missing node definition for ${nodeId}.`));
378
+ const downstream = invariant(adjacency.get(nodeId), `Missing adjacency list for ${nodeId}.`).sort((left, right) => invariant(order.get(left), `Missing order index for ${left}.`) -
379
+ invariant(order.get(right), `Missing order index for ${right}.`));
380
+ downstream.forEach((item) => {
381
+ const nextIndegree = invariant(indegree.get(item), `Missing indegree entry for ${item}.`) - 1;
382
+ indegree.set(item, nextIndegree);
383
+ if (nextIndegree === 0) {
384
+ queue.push(item);
385
+ }
386
+ });
387
+ }
388
+ if (ordered.length !== nodes.length) {
389
+ warnings.push('Dependency cycle detected; preserving original order for cyclic remainder.');
390
+ const seen = new Set(ordered.map((node) => node.node_id));
391
+ nodes.forEach((node) => {
392
+ if (!seen.has(node.node_id)) {
393
+ ordered.push(node);
394
+ }
395
+ });
396
+ }
397
+ return { ordered, warnings };
398
+ }
399
+ function unresolvedResolution(reason) {
400
+ return {
401
+ resolution: 'unresolved',
402
+ selected_candidate: null,
403
+ reason,
404
+ boundary_reason: 'unresolved',
405
+ };
406
+ }
407
+ function selectResolution(node, orchestration) {
408
+ const requestedAction = node.requested_action;
409
+ const resultingCandidate = node.existing_resulting_process_candidates[0] ?? null;
410
+ const processCandidate = node.existing_process_candidates[0] ?? null;
411
+ const modelCandidate = node.existing_lifecyclemodel_candidates[0] ?? null;
412
+ const allowProcessBuild = Boolean(orchestration.allow_process_build);
413
+ const allowSubmodelBuild = Boolean(orchestration.allow_submodel_build);
414
+ const reuseResultingProcessFirst = orchestration.reuse_resulting_process_first !== false;
415
+ if (requestedAction === 'cutoff') {
416
+ return {
417
+ resolution: 'cutoff',
418
+ selected_candidate: null,
419
+ reason: 'Explicit cutoff requested.',
420
+ boundary_reason: 'explicit_cutoff',
421
+ };
422
+ }
423
+ if (requestedAction === 'unresolved') {
424
+ return unresolvedResolution('Explicit unresolved marker provided.');
425
+ }
426
+ if (requestedAction === 'reuse_existing_resulting_process') {
427
+ if (!resultingCandidate) {
428
+ return unresolvedResolution('requested reuse_existing_resulting_process but no candidate was provided');
429
+ }
430
+ return {
431
+ resolution: 'reused_existing_resulting_process',
432
+ selected_candidate: resultingCandidate,
433
+ reason: 'Requested resulting-process reuse.',
434
+ boundary_reason: 'collapsed_at_resulting_process',
435
+ };
436
+ }
437
+ if (requestedAction === 'reuse_existing_process') {
438
+ if (!processCandidate) {
439
+ return unresolvedResolution('requested reuse_existing_process but no candidate was provided');
440
+ }
441
+ return {
442
+ resolution: 'reused_existing_process',
443
+ selected_candidate: processCandidate,
444
+ reason: 'Requested process reuse.',
445
+ boundary_reason: 'collapsed_at_existing_process',
446
+ };
447
+ }
448
+ if (requestedAction === 'reuse_existing_model') {
449
+ if (!modelCandidate) {
450
+ return unresolvedResolution('requested reuse_existing_model but no lifecyclemodel candidate was provided');
451
+ }
452
+ return {
453
+ resolution: 'reused_existing_model',
454
+ selected_candidate: modelCandidate,
455
+ reason: 'Requested lifecyclemodel reuse.',
456
+ boundary_reason: 'collapsed_at_existing_model',
457
+ };
458
+ }
459
+ if (requestedAction === 'build_process') {
460
+ if (!node.process_builder) {
461
+ return unresolvedResolution('requested build_process but process_builder config is missing');
462
+ }
463
+ if (!allowProcessBuild) {
464
+ return unresolvedResolution('requested build_process but orchestration.allow_process_build=false');
465
+ }
466
+ return {
467
+ resolution: 'build_via_process_automated_builder',
468
+ selected_candidate: null,
469
+ reason: 'Requested process build.',
470
+ boundary_reason: null,
471
+ };
472
+ }
473
+ if (requestedAction === 'build_submodel') {
474
+ if (!node.submodel_builder) {
475
+ return unresolvedResolution('requested build_submodel but submodel_builder config is missing');
476
+ }
477
+ if (!allowSubmodelBuild) {
478
+ return unresolvedResolution('requested build_submodel but orchestration.allow_submodel_build=false');
479
+ }
480
+ return {
481
+ resolution: 'build_via_lifecyclemodel_automated_builder',
482
+ selected_candidate: null,
483
+ reason: 'Requested submodel build.',
484
+ boundary_reason: null,
485
+ };
486
+ }
487
+ if (reuseResultingProcessFirst && resultingCandidate) {
488
+ return {
489
+ resolution: 'reused_existing_resulting_process',
490
+ selected_candidate: resultingCandidate,
491
+ reason: 'Auto-selected highest scoring resulting process candidate.',
492
+ boundary_reason: 'collapsed_at_resulting_process',
493
+ };
494
+ }
495
+ if (processCandidate) {
496
+ return {
497
+ resolution: 'reused_existing_process',
498
+ selected_candidate: processCandidate,
499
+ reason: 'Auto-selected highest scoring existing process candidate.',
500
+ boundary_reason: 'collapsed_at_existing_process',
501
+ };
502
+ }
503
+ if ((node.kind === 'lifecyclemodel' || node.kind === 'subsystem') && modelCandidate) {
504
+ return {
505
+ resolution: 'reused_existing_model',
506
+ selected_candidate: modelCandidate,
507
+ reason: 'Auto-selected highest scoring lifecyclemodel candidate.',
508
+ boundary_reason: 'collapsed_at_existing_model',
509
+ };
510
+ }
511
+ if ((node.kind === 'lifecyclemodel' || node.kind === 'subsystem') &&
512
+ node.submodel_builder &&
513
+ allowSubmodelBuild) {
514
+ return {
515
+ resolution: 'build_via_lifecyclemodel_automated_builder',
516
+ selected_candidate: null,
517
+ reason: 'No reusable model/process matched; scheduling lifecyclemodel builder.',
518
+ boundary_reason: null,
519
+ };
520
+ }
521
+ if (node.process_builder && allowProcessBuild) {
522
+ return {
523
+ resolution: 'build_via_process_automated_builder',
524
+ selected_candidate: null,
525
+ reason: 'No reusable process matched; scheduling process builder.',
526
+ boundary_reason: null,
527
+ };
528
+ }
529
+ if (modelCandidate) {
530
+ return {
531
+ resolution: 'reused_existing_model',
532
+ selected_candidate: modelCandidate,
533
+ reason: 'Fallback to lifecyclemodel candidate after no process candidate matched.',
534
+ boundary_reason: 'collapsed_at_existing_model',
535
+ };
536
+ }
537
+ return unresolvedResolution('No reusable candidate or build config satisfied the node policy');
538
+ }
539
+ function shouldRunProjector(node, resolution) {
540
+ if (!node.projector) {
541
+ return false;
542
+ }
543
+ if (node.projector.run_always) {
544
+ return true;
545
+ }
546
+ return (resolution === 'build_via_lifecyclemodel_automated_builder' ||
547
+ resolution === 'reused_existing_model');
548
+ }
549
+ function validateRequestShape(request) {
550
+ const goal = requireObject(request.goal, 'goal');
551
+ if (!nonEmptyString(goal.name)) {
552
+ throw new CliError('goal.name is required.', {
553
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
554
+ exitCode: 2,
555
+ });
556
+ }
557
+ const root = requireObject(request.root, 'root');
558
+ const rootKind = requireEnum(root.kind, ['reference_flow', 'process', 'lifecyclemodel', 'resulting_process'], 'root.kind');
559
+ const requiredRootField = rootKind === 'reference_flow'
560
+ ? 'flow'
561
+ : rootKind === 'process'
562
+ ? 'process'
563
+ : rootKind === 'lifecyclemodel'
564
+ ? 'lifecyclemodel'
565
+ : 'resulting_process';
566
+ const requiredRootEntity = requireObject(root[requiredRootField], `root.${requiredRootField}`);
567
+ if (!nonEmptyString(requiredRootEntity.id)) {
568
+ throw new CliError(`root.${requiredRootField}.id is required.`, {
569
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
570
+ exitCode: 2,
571
+ });
572
+ }
573
+ const orchestration = requireObject(request.orchestration, 'orchestration');
574
+ requireEnum(orchestration.mode, ['collapsed', 'expanded', 'hybrid'], 'orchestration.mode');
575
+ requireInteger(orchestration.max_depth, 'orchestration.max_depth');
576
+ requireBoolean(orchestration.reuse_resulting_process_first, 'orchestration.reuse_resulting_process_first');
577
+ requireBoolean(orchestration.allow_process_build, 'orchestration.allow_process_build');
578
+ requireBoolean(orchestration.allow_submodel_build, 'orchestration.allow_submodel_build');
579
+ requireBoolean(orchestration.pin_child_versions, 'orchestration.pin_child_versions');
580
+ requireBoolean(orchestration.stop_at_elementary_flow, 'orchestration.stop_at_elementary_flow');
581
+ const publish = requireObject(request.publish, 'publish');
582
+ requireEnum(publish.intent, ['dry_run', 'prepare_only', 'publish'], 'publish.intent');
583
+ if (request.nodes !== undefined && !Array.isArray(request.nodes)) {
584
+ throw new CliError('nodes must be an array when provided.', {
585
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
586
+ exitCode: 2,
587
+ });
588
+ }
589
+ if (request.edges !== undefined && !Array.isArray(request.edges)) {
590
+ throw new CliError('edges must be an array when provided.', {
591
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
592
+ exitCode: 2,
593
+ });
594
+ }
595
+ }
596
+ function buildPlan(request, requestPath, outDir, now) {
597
+ validateRequestShape(request);
598
+ const requestDir = path.dirname(requestPath);
599
+ const goal = copyJson(requireObject(request.goal, 'goal'));
600
+ const root = copyJson(requireObject(request.root, 'root'));
601
+ const orchestration = copyJson(requireObject(request.orchestration, 'orchestration'));
602
+ const publish = copyJson(requireObject(request.publish, 'publish'));
603
+ const candidateSources = {
604
+ ...defaultCandidateSources(),
605
+ ...(isRecord(request.candidate_sources) ? copyJson(request.candidate_sources) : {}),
606
+ };
607
+ const rootNode = deriveRootNode(root, goal, requestDir);
608
+ const requestedNodes = ensureList(request.nodes).filter((entry) => isRecord(entry));
609
+ const rawNodes = [];
610
+ if (!requestedNodes.some((entry) => firstNonEmpty(entry.node_id, entry.id) === rootNode.node_id)) {
611
+ rawNodes.push({
612
+ node_id: rootNode.node_id,
613
+ kind: rootNode.kind,
614
+ label: rootNode.label,
615
+ entity: rootNode.entity,
616
+ requested_action: rootNode.requested_action,
617
+ depends_on: rootNode.depends_on,
618
+ parent_node_id: rootNode.parent_node_id,
619
+ existing_resulting_process_candidates: rootNode.existing_resulting_process_candidates,
620
+ existing_process_candidates: rootNode.existing_process_candidates,
621
+ existing_lifecyclemodel_candidates: rootNode.existing_lifecyclemodel_candidates,
622
+ process_builder: rootNode.process_builder,
623
+ submodel_builder: rootNode.submodel_builder,
624
+ projector: rootNode.projector,
625
+ });
626
+ }
627
+ rawNodes.push(...requestedNodes.map((entry) => copyJson(entry)));
628
+ const nodes = [];
629
+ const seenNodeIds = new Set();
630
+ rawNodes.forEach((rawNode, index) => {
631
+ const normalizedNode = normalizeNode(rawNode, index + 1, requestDir);
632
+ if (seenNodeIds.has(normalizedNode.node_id)) {
633
+ if (normalizedNode.node_id === 'root') {
634
+ return;
635
+ }
636
+ throw new CliError(`Duplicate node_id: ${normalizedNode.node_id}`, {
637
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
638
+ exitCode: 2,
639
+ });
640
+ }
641
+ seenNodeIds.add(normalizedNode.node_id);
642
+ nodes.push(normalizedNode);
643
+ });
644
+ const { ordered, warnings } = topoSortNodes(nodes);
645
+ const edges = buildEdges(ensureList(request.edges), ordered);
646
+ const invocations = [];
647
+ const unresolved = [];
648
+ const boundaries = [];
649
+ ordered.forEach((node) => {
650
+ const resolution = selectResolution(node, orchestration);
651
+ node.resolution = resolution.resolution;
652
+ node.resolution_reason = resolution.reason;
653
+ node.selected_candidate = resolution.selected_candidate;
654
+ node.boundary_reason = resolution.boundary_reason;
655
+ node.planned_invocations = [];
656
+ if (resolution.boundary_reason) {
657
+ boundaries.push({
658
+ node_id: node.node_id,
659
+ reason: resolution.boundary_reason,
660
+ });
661
+ }
662
+ if (resolution.resolution === 'unresolved') {
663
+ unresolved.push({
664
+ node_id: node.node_id,
665
+ label: node.label,
666
+ reason: resolution.reason,
667
+ });
668
+ return;
669
+ }
670
+ if (resolution.resolution === 'build_via_process_automated_builder') {
671
+ const invocationId = `${node.node_id}:process-builder`;
672
+ const artifactDir = path.resolve(requestDir, path.join('artifacts', 'process_from_flow', safeSlug(`${rootNode.node_id}-${node.node_id}`)));
673
+ invocations.push({
674
+ invocation_id: invocationId,
675
+ node_id: node.node_id,
676
+ kind: 'process_builder',
677
+ config: serializeInvocationConfig(node.process_builder, `Node ${node.node_id} resolved to process_builder without process_builder config.`, { node_id: node.node_id, resolution: resolution.resolution }),
678
+ artifact_dir: artifactDir,
679
+ });
680
+ node.planned_invocations.push(invocationId);
681
+ }
682
+ if (resolution.resolution === 'build_via_lifecyclemodel_automated_builder') {
683
+ const invocationId = `${node.node_id}:lifecyclemodel-builder`;
684
+ const artifactDir = path.resolve(node.submodel_builder?.out_dir ??
685
+ path.join(outDir, 'downstream', safeSlug(node.node_id), 'lifecyclemodel-builder'));
686
+ invocations.push({
687
+ invocation_id: invocationId,
688
+ node_id: node.node_id,
689
+ kind: 'lifecyclemodel_builder',
690
+ config: serializeInvocationConfig(node.submodel_builder, `Node ${node.node_id} resolved to lifecyclemodel_builder without submodel_builder config.`, { node_id: node.node_id, resolution: resolution.resolution }),
691
+ artifact_dir: artifactDir,
692
+ });
693
+ node.planned_invocations.push(invocationId);
694
+ }
695
+ if (node.resolution && shouldRunProjector(node, node.resolution)) {
696
+ const invocationId = `${node.node_id}:projector`;
697
+ const dependsOnInvocationId = node.resolution === 'build_via_lifecyclemodel_automated_builder'
698
+ ? `${node.node_id}:lifecyclemodel-builder`
699
+ : undefined;
700
+ const artifactDir = path.resolve(node.projector?.out_dir ??
701
+ path.join(outDir, 'downstream', safeSlug(node.node_id), 'projector'));
702
+ invocations.push({
703
+ invocation_id: invocationId,
704
+ node_id: node.node_id,
705
+ kind: 'projector',
706
+ config: serializeInvocationConfig(node.projector, `Node ${node.node_id} scheduled projector without projector config.`, { node_id: node.node_id, resolution: resolution.resolution }),
707
+ artifact_dir: artifactDir,
708
+ depends_on_invocation_id: dependsOnInvocationId,
709
+ });
710
+ node.planned_invocations.push(invocationId);
711
+ }
712
+ });
713
+ return {
714
+ skill: 'lifecyclemodel-recursive-orchestrator',
715
+ request_id: nonEmptyString(request.request_id) ??
716
+ `run-${nowIso(now).replace(/[-:.]/gu, '').replace(/Z$/u, 'Z')}`,
717
+ created_at: nowIso(now),
718
+ request_file: requestPath,
719
+ goal,
720
+ root,
721
+ orchestration,
722
+ candidate_sources: candidateSources,
723
+ publish: normalizePublishConfig(publish),
724
+ notes: ensureList(request.notes)
725
+ .map((entry) => nonEmptyString(entry))
726
+ .filter((entry) => Boolean(entry)),
727
+ nodes: ordered,
728
+ edges,
729
+ invocations,
730
+ planner_summary: {
731
+ status: 'planned',
732
+ message: 'Request validated, nodes normalized, and downstream invocations scheduled.',
733
+ },
734
+ warnings,
735
+ unresolved,
736
+ boundaries,
737
+ artifacts: {
738
+ root: outDir,
739
+ request_normalized: path.join(outDir, 'request.normalized.json'),
740
+ assembly_plan: path.join(outDir, 'assembly-plan.json'),
741
+ graph_manifest: path.join(outDir, 'graph-manifest.json'),
742
+ lineage_manifest: path.join(outDir, 'lineage-manifest.json'),
743
+ boundary_report: path.join(outDir, 'boundary-report.json'),
744
+ invocations_dir: path.join(outDir, 'invocations'),
745
+ publish_bundle: path.join(outDir, 'publish-bundle.json'),
746
+ publish_summary: path.join(outDir, 'publish-summary.json'),
747
+ },
748
+ summary: {
749
+ node_count: ordered.length,
750
+ edge_count: edges.length,
751
+ invocation_count: invocations.length,
752
+ unresolved_count: unresolved.length,
753
+ },
754
+ };
755
+ }
756
+ function executionStatusByNode(plan, executionResults) {
757
+ const byNode = new Map();
758
+ executionResults.forEach((result) => {
759
+ const list = byNode.get(result.node_id) ?? [];
760
+ list.push(result);
761
+ byNode.set(result.node_id, list);
762
+ });
763
+ const statuses = {};
764
+ plan.nodes.forEach((node) => {
765
+ const nodeResults = byNode.get(node.node_id) ?? [];
766
+ if (node.resolution === 'unresolved') {
767
+ statuses[node.node_id] = 'unresolved';
768
+ return;
769
+ }
770
+ if (node.resolution === 'cutoff') {
771
+ statuses[node.node_id] = 'cutoff';
772
+ return;
773
+ }
774
+ if (node.resolution?.startsWith('reused_')) {
775
+ statuses[node.node_id] = 'reused';
776
+ return;
777
+ }
778
+ if (nodeResults.length === 0) {
779
+ statuses[node.node_id] = 'planned';
780
+ return;
781
+ }
782
+ if (nodeResults.some((result) => result.status === 'failed')) {
783
+ statuses[node.node_id] = 'failed';
784
+ return;
785
+ }
786
+ if (nodeResults.some((result) => result.status.startsWith('skipped'))) {
787
+ statuses[node.node_id] = 'blocked';
788
+ return;
789
+ }
790
+ if (nodeResults.every((result) => result.status === 'success')) {
791
+ statuses[node.node_id] = 'completed';
792
+ return;
793
+ }
794
+ statuses[node.node_id] = 'incomplete';
795
+ });
796
+ return statuses;
797
+ }
798
+ function collectResultingProcessRelations(executionResults) {
799
+ const relations = [];
800
+ executionResults.forEach((result) => {
801
+ const bundlePath = nonEmptyString(result.artifacts?.projection_bundle);
802
+ if (!bundlePath || !existsSync(bundlePath)) {
803
+ return;
804
+ }
805
+ const bundle = readJsonArtifact(bundlePath);
806
+ if (!isRecord(bundle)) {
807
+ return;
808
+ }
809
+ ensureList(bundle.relations).forEach((relation) => {
810
+ if (!isRecord(relation)) {
811
+ return;
812
+ }
813
+ relations.push({
814
+ ...copyJson(relation),
815
+ node_id: result.node_id,
816
+ });
817
+ });
818
+ });
819
+ return relations;
820
+ }
821
+ function buildGraphManifest(plan, executionResults) {
822
+ const nodeStatuses = executionStatusByNode(plan, executionResults);
823
+ return {
824
+ root: {
825
+ request_id: plan.request_id,
826
+ goal: copyJson(plan.goal),
827
+ mode: plan.orchestration.mode,
828
+ max_depth: plan.orchestration.max_depth,
829
+ },
830
+ nodes: plan.nodes.map((node) => ({
831
+ node_id: node.node_id,
832
+ label: node.label,
833
+ kind: node.kind,
834
+ resolution: node.resolution,
835
+ execution_status: invariant(nodeStatuses[node.node_id], `Missing execution status for node ${node.node_id}.`, { node_id: node.node_id }),
836
+ selected_candidate: copyJson(node.selected_candidate ?? null),
837
+ depends_on: copyJson(node.depends_on),
838
+ boundary_reason: node.boundary_reason ?? null,
839
+ })),
840
+ edges: copyJson(plan.edges),
841
+ boundaries: copyJson(plan.boundaries),
842
+ unresolved: copyJson(plan.unresolved),
843
+ stats: {
844
+ node_count: plan.nodes.length,
845
+ edge_count: plan.edges.length,
846
+ invocation_count: plan.invocations.length,
847
+ unresolved_count: plan.unresolved.length,
848
+ completed_invocation_count: executionResults.filter((result) => result.status === 'success')
849
+ .length,
850
+ },
851
+ };
852
+ }
853
+ function buildLineageManifest(plan, executionResults) {
854
+ const executionByInvocation = new Map(executionResults.map((result) => [result.invocation_id, result]));
855
+ const nodeStatuses = executionStatusByNode(plan, executionResults);
856
+ const publishedDependencies = plan.nodes
857
+ .filter((node) => isRecord(node.selected_candidate))
858
+ .map((node) => ({
859
+ node_id: node.node_id,
860
+ dependency_type: node.resolution,
861
+ candidate_id: node.selected_candidate?.id ?? null,
862
+ candidate_version: nonEmptyString(node.selected_candidate?.version) ?? null,
863
+ }));
864
+ return {
865
+ root_request: {
866
+ request_id: plan.request_id,
867
+ goal: copyJson(plan.goal),
868
+ root: copyJson(plan.root),
869
+ orchestration: copyJson(plan.orchestration),
870
+ publish: copyJson(plan.publish),
871
+ },
872
+ builder_invocations: plan.invocations.map((invocation) => {
873
+ const result = executionByInvocation.get(invocation.invocation_id);
874
+ return {
875
+ invocation_id: invocation.invocation_id,
876
+ node_id: invocation.node_id,
877
+ kind: invocation.kind,
878
+ artifact_dir: invocation.artifact_dir,
879
+ status: result?.status ?? 'planned',
880
+ exit_code: result?.exit_code ?? null,
881
+ result_file: result?.result_file ?? null,
882
+ };
883
+ }),
884
+ node_resolution_log: plan.nodes.map((node) => ({
885
+ node_id: node.node_id,
886
+ label: node.label,
887
+ resolution: node.resolution,
888
+ reason: node.resolution_reason,
889
+ selected_candidate: copyJson(node.selected_candidate ?? null),
890
+ execution_status: invariant(nodeStatuses[node.node_id], `Missing execution status for node ${node.node_id}.`, { node_id: node.node_id }),
891
+ })),
892
+ published_dependencies: publishedDependencies,
893
+ resulting_process_relations: collectResultingProcessRelations(executionResults),
894
+ unresolved_history: copyJson(plan.unresolved),
895
+ };
896
+ }
897
+ function buildBoundaryReport(plan, executionResults) {
898
+ return {
899
+ request_id: plan.request_id,
900
+ generated_at: nowIso(),
901
+ boundaries: copyJson(plan.boundaries),
902
+ unresolved: copyJson(plan.unresolved),
903
+ execution_summary: {
904
+ successful_invocations: executionResults.filter((result) => result.status === 'success')
905
+ .length,
906
+ failed_invocations: executionResults.filter((result) => result.status === 'failed').length,
907
+ blocked_invocations: executionResults.filter((result) => result.status.startsWith('skipped'))
908
+ .length,
909
+ },
910
+ };
911
+ }
912
+ function writePlanArtifacts(normalizedRequest, plan, graphManifest, lineageManifest, boundaryReport) {
913
+ writeJsonArtifact(plan.artifacts.request_normalized, normalizedRequest);
914
+ writeJsonArtifact(plan.artifacts.assembly_plan, plan);
915
+ writeJsonArtifact(plan.artifacts.graph_manifest, graphManifest);
916
+ writeJsonArtifact(plan.artifacts.lineage_manifest, lineageManifest);
917
+ writeJsonArtifact(plan.artifacts.boundary_report, boundaryReport);
918
+ }
919
+ function requireFile(filePath, label) {
920
+ if (!existsSync(filePath)) {
921
+ throw new CliError(`Missing ${label}: ${filePath}`, {
922
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_MISSING_FILE',
923
+ exitCode: 2,
924
+ details: { filePath, label },
925
+ });
926
+ }
927
+ }
928
+ function writeRequestFile(filePath, payload) {
929
+ mkdirSync(path.dirname(filePath), { recursive: true });
930
+ writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
931
+ return filePath;
932
+ }
933
+ function parseInlineJson(value, label) {
934
+ if (isRecord(value)) {
935
+ return copyJson(value);
936
+ }
937
+ if (typeof value === 'string' && value.trim()) {
938
+ try {
939
+ const parsed = JSON.parse(value);
940
+ if (!isRecord(parsed)) {
941
+ throw new Error('parsed value is not an object');
942
+ }
943
+ return parsed;
944
+ }
945
+ catch (error) {
946
+ throw new CliError(`Invalid ${label} JSON string.`, {
947
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
948
+ exitCode: 2,
949
+ details: { value, message: String(error) },
950
+ });
951
+ }
952
+ }
953
+ throw new CliError(`${label} must be a JSON object or JSON string.`, {
954
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
955
+ exitCode: 2,
956
+ details: { value },
957
+ });
958
+ }
959
+ function buildProcessBuilderArtifacts(report) {
960
+ return {
961
+ run_id: report.run_id,
962
+ run_root: report.run_root,
963
+ state_file: report.files.state,
964
+ exports_dir: path.join(report.run_root, 'exports', 'processes'),
965
+ report: report.files.report,
966
+ };
967
+ }
968
+ function buildLifecyclemodelBuilderArtifacts(report) {
969
+ return {
970
+ run_root: report.run_root,
971
+ run_id: report.run_id,
972
+ run_plan: report.files.run_plan,
973
+ resolved_manifest: report.files.resolved_manifest,
974
+ produced_model_files: report.local_build_reports.map((entry) => entry.model_file),
975
+ process_catalog_files: report.local_build_reports.map((entry) => entry.process_catalog_file),
976
+ source_run_dirs: report.local_build_reports.map((entry) => entry.run_dir),
977
+ report_files: report.local_build_reports.map((entry) => entry.summary_file),
978
+ report: report.files.report,
979
+ };
980
+ }
981
+ function buildProjectorArtifacts(report) {
982
+ return {
983
+ out_dir: report.out_dir,
984
+ projection_bundle: report.files.process_projection_bundle,
985
+ projection_report: report.files.projection_report,
986
+ request_normalized: report.files.normalized_request,
987
+ };
988
+ }
989
+ async function executeProcessBuilderInvocation(invocation, plan, resultFile, now) {
990
+ const config = invocation.config;
991
+ const requestsDir = path.join(plan.artifacts.invocations_dir, 'requests');
992
+ const slug = safeSlug(invocation.invocation_id);
993
+ let flowFile = nonEmptyString(config.flow_file);
994
+ if (!flowFile) {
995
+ const flowPayload = parseInlineJson(config.flow_json, `${invocation.invocation_id} flow_json`);
996
+ flowFile = writeRequestFile(path.join(requestsDir, `${slug}.flow.json`), flowPayload);
997
+ }
998
+ const request = {
999
+ flow_file: flowFile,
1000
+ operation: 'produce',
1001
+ };
1002
+ const requestFile = writeRequestFile(path.join(requestsDir, `${slug}.process-auto-build.request.json`), request);
1003
+ const runId = nonEmptyString(config.run_id) ?? `${safeSlug(plan.request_id)}-${safeSlug(invocation.node_id)}`;
1004
+ const report = await runProcessAutoBuild({
1005
+ inputPath: requestFile,
1006
+ inputValue: request,
1007
+ outDir: invocation.artifact_dir,
1008
+ now,
1009
+ requestIdOverride: `${plan.request_id}:${invocation.node_id}`,
1010
+ runIdOverride: runId,
1011
+ });
1012
+ const result = {
1013
+ invocation_id: invocation.invocation_id,
1014
+ node_id: invocation.node_id,
1015
+ kind: invocation.kind,
1016
+ status: 'success',
1017
+ exit_code: 0,
1018
+ result_file: resultFile,
1019
+ planned_artifacts: {
1020
+ run_id: runId,
1021
+ run_root: report.run_root,
1022
+ },
1023
+ artifacts: buildProcessBuilderArtifacts(report),
1024
+ };
1025
+ writeJsonArtifact(resultFile, result);
1026
+ return result;
1027
+ }
1028
+ async function executeLifecyclemodelBuilderInvocation(invocation, resultFile, now) {
1029
+ const config = invocation.config;
1030
+ const manifest = nonEmptyString(config.manifest);
1031
+ if (!manifest) {
1032
+ throw new CliError(`Invocation ${invocation.invocation_id} is missing submodel_builder.manifest.`, {
1033
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INVALID_REQUEST',
1034
+ exitCode: 2,
1035
+ });
1036
+ }
1037
+ requireFile(manifest, 'lifecyclemodel builder manifest');
1038
+ if (config.dry_run === true) {
1039
+ const result = {
1040
+ invocation_id: invocation.invocation_id,
1041
+ node_id: invocation.node_id,
1042
+ kind: invocation.kind,
1043
+ status: 'success',
1044
+ exit_code: 0,
1045
+ result_file: resultFile,
1046
+ dry_run: true,
1047
+ planned_artifacts: {
1048
+ out_dir: invocation.artifact_dir,
1049
+ manifest,
1050
+ },
1051
+ artifacts: {
1052
+ out_dir: invocation.artifact_dir,
1053
+ run_plan: null,
1054
+ resolved_manifest: null,
1055
+ produced_model_files: [],
1056
+ process_catalog_files: [],
1057
+ report_files: [],
1058
+ },
1059
+ };
1060
+ writeJsonArtifact(resultFile, result);
1061
+ return result;
1062
+ }
1063
+ const report = await runLifecyclemodelAutoBuild({
1064
+ inputPath: manifest,
1065
+ outDir: invocation.artifact_dir,
1066
+ now,
1067
+ });
1068
+ const result = {
1069
+ invocation_id: invocation.invocation_id,
1070
+ node_id: invocation.node_id,
1071
+ kind: invocation.kind,
1072
+ status: 'success',
1073
+ exit_code: 0,
1074
+ result_file: resultFile,
1075
+ planned_artifacts: {
1076
+ out_dir: invocation.artifact_dir,
1077
+ manifest,
1078
+ },
1079
+ artifacts: buildLifecyclemodelBuilderArtifacts(report),
1080
+ };
1081
+ writeJsonArtifact(resultFile, result);
1082
+ return result;
1083
+ }
1084
+ function inferProjectorModelFile(invocation, executionMap) {
1085
+ const explicit = nonEmptyString(invocation.config.model_file);
1086
+ if (explicit) {
1087
+ return explicit;
1088
+ }
1089
+ const dependencyId = nonEmptyString(invocation.depends_on_invocation_id);
1090
+ if (!dependencyId) {
1091
+ return null;
1092
+ }
1093
+ const dependency = executionMap.get(dependencyId);
1094
+ const modelFiles = ensureList(dependency?.artifacts?.produced_model_files)
1095
+ .map((entry) => nonEmptyString(entry))
1096
+ .filter((entry) => Boolean(entry));
1097
+ return modelFiles[0] ?? null;
1098
+ }
1099
+ function collectProjectorDependencyArtifacts(invocation, executionMap) {
1100
+ const dependencyId = nonEmptyString(invocation.depends_on_invocation_id);
1101
+ const dependency = executionMap.get(dependencyId ?? '');
1102
+ return {
1103
+ processCatalogPath: ensureList(dependency?.artifacts?.process_catalog_files)
1104
+ .map((entry) => nonEmptyString(entry))
1105
+ .filter((entry) => Boolean(entry))[0] ?? null,
1106
+ sourceRunDirs: ensureList(dependency?.artifacts?.source_run_dirs)
1107
+ .map((entry) => nonEmptyString(entry))
1108
+ .filter((entry) => Boolean(entry)),
1109
+ };
1110
+ }
1111
+ function buildProjectorRequest(invocation, modelFile, plan, processCatalogPath, sourceRunDirs) {
1112
+ return {
1113
+ source_model: {
1114
+ json_ordered_path: modelFile,
1115
+ },
1116
+ projection: {
1117
+ mode: invocation.config.projection_role === 'all' ? 'all-subproducts' : 'primary-only',
1118
+ metadata_overrides: {
1119
+ projection_source: 'lifecyclemodel_orchestrate',
1120
+ },
1121
+ attach_graph_snapshot: false,
1122
+ },
1123
+ process_sources: {
1124
+ ...(processCatalogPath ? { process_catalog_path: processCatalogPath } : {}),
1125
+ ...(sourceRunDirs.length > 0 ? { run_dirs: sourceRunDirs } : {}),
1126
+ allow_remote_lookup: false,
1127
+ },
1128
+ publish: {
1129
+ intent: plan.publish.intent,
1130
+ prepare_process_payloads: plan.publish.prepare_resulting_process_payload !== false,
1131
+ prepare_relation_payloads: plan.publish.prepare_relation_payload !== false,
1132
+ },
1133
+ };
1134
+ }
1135
+ async function executeProjectorInvocation(invocation, plan, executionMap, resultFile, now, env, fetchImpl) {
1136
+ const requestPath = nonEmptyString(invocation.config.request);
1137
+ let projectorRequestPath = requestPath;
1138
+ if (!projectorRequestPath) {
1139
+ const modelFile = inferProjectorModelFile(invocation, executionMap);
1140
+ if (!modelFile) {
1141
+ throw new CliError(`Invocation ${invocation.invocation_id} requires projector.request or a prior lifecyclemodel build result.`, {
1142
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_PROJECTOR_REQUEST_REQUIRED',
1143
+ exitCode: 2,
1144
+ });
1145
+ }
1146
+ const { processCatalogPath, sourceRunDirs } = collectProjectorDependencyArtifacts(invocation, executionMap);
1147
+ projectorRequestPath = writeRequestFile(path.join(plan.artifacts.invocations_dir, 'requests', `${safeSlug(invocation.invocation_id)}.projector.request.json`), buildProjectorRequest(invocation, modelFile, plan, processCatalogPath, sourceRunDirs));
1148
+ }
1149
+ else {
1150
+ requireFile(projectorRequestPath, 'projector request');
1151
+ }
1152
+ const report = await runLifecyclemodelBuildResultingProcess({
1153
+ inputPath: projectorRequestPath,
1154
+ outDir: invocation.artifact_dir,
1155
+ now,
1156
+ env,
1157
+ fetchImpl,
1158
+ });
1159
+ const result = {
1160
+ invocation_id: invocation.invocation_id,
1161
+ node_id: invocation.node_id,
1162
+ kind: invocation.kind,
1163
+ status: 'success',
1164
+ exit_code: 0,
1165
+ result_file: resultFile,
1166
+ planned_artifacts: {
1167
+ out_dir: invocation.artifact_dir,
1168
+ request: projectorRequestPath,
1169
+ },
1170
+ artifacts: buildProjectorArtifacts(report),
1171
+ };
1172
+ writeJsonArtifact(resultFile, result);
1173
+ return result;
1174
+ }
1175
+ function recordInvocationResult(invocation, result, results, executionMap) {
1176
+ invocation.last_status = result.status;
1177
+ invocation.last_exit_code = result.exit_code;
1178
+ invocation.last_result_file = result.result_file;
1179
+ invocation.artifacts = result.artifacts;
1180
+ results.push(result);
1181
+ executionMap.set(invocation.invocation_id, result);
1182
+ }
1183
+ async function executePlan(plan, now, env, fetchImpl) {
1184
+ mkdirSync(plan.artifacts.invocations_dir, { recursive: true });
1185
+ const results = [];
1186
+ const executionMap = new Map();
1187
+ const failFast = plan.orchestration.fail_fast !== false;
1188
+ let stopRemaining = false;
1189
+ for (const invocation of plan.invocations) {
1190
+ const resultFile = path.join(plan.artifacts.invocations_dir, `${safeSlug(invocation.invocation_id)}.json`);
1191
+ if (stopRemaining) {
1192
+ const skipped = {
1193
+ invocation_id: invocation.invocation_id,
1194
+ node_id: invocation.node_id,
1195
+ kind: invocation.kind,
1196
+ status: 'skipped_due_to_fail_fast',
1197
+ exit_code: null,
1198
+ result_file: resultFile,
1199
+ };
1200
+ writeJsonArtifact(resultFile, skipped);
1201
+ recordInvocationResult(invocation, skipped, results, executionMap);
1202
+ continue;
1203
+ }
1204
+ const dependencyId = nonEmptyString(invocation.depends_on_invocation_id);
1205
+ if (dependencyId) {
1206
+ const dependency = executionMap.get(dependencyId);
1207
+ if (dependency && dependency.status !== 'success') {
1208
+ const blocked = {
1209
+ invocation_id: invocation.invocation_id,
1210
+ node_id: invocation.node_id,
1211
+ kind: invocation.kind,
1212
+ status: `skipped_due_to_dependency_${dependency.status}`,
1213
+ exit_code: null,
1214
+ result_file: resultFile,
1215
+ };
1216
+ writeJsonArtifact(resultFile, blocked);
1217
+ recordInvocationResult(invocation, blocked, results, executionMap);
1218
+ continue;
1219
+ }
1220
+ }
1221
+ try {
1222
+ let result;
1223
+ if (invocation.kind === 'process_builder') {
1224
+ result = await executeProcessBuilderInvocation(invocation, plan, resultFile, now);
1225
+ }
1226
+ else if (invocation.kind === 'lifecyclemodel_builder') {
1227
+ result = await executeLifecyclemodelBuilderInvocation(invocation, resultFile, now);
1228
+ }
1229
+ else {
1230
+ result = await executeProjectorInvocation(invocation, plan, executionMap, resultFile, now, env, fetchImpl);
1231
+ }
1232
+ recordInvocationResult(invocation, result, results, executionMap);
1233
+ }
1234
+ catch (error) {
1235
+ const failure = normalizeInvocationFailure(error);
1236
+ const failed = {
1237
+ invocation_id: invocation.invocation_id,
1238
+ node_id: invocation.node_id,
1239
+ kind: invocation.kind,
1240
+ status: 'failed',
1241
+ exit_code: failure.exit_code,
1242
+ result_file: resultFile,
1243
+ error: failure.error,
1244
+ };
1245
+ writeJsonArtifact(resultFile, failed);
1246
+ recordInvocationResult(invocation, failed, results, executionMap);
1247
+ if (failFast) {
1248
+ stopRemaining = true;
1249
+ }
1250
+ }
1251
+ }
1252
+ const executionSummary = {
1253
+ executed_at: nowIso(now),
1254
+ successful_invocations: results.filter((result) => result.status === 'success').length,
1255
+ failed_invocations: results.filter((result) => result.status === 'failed').length,
1256
+ blocked_invocations: results.filter((result) => result.status.startsWith('skipped')).length,
1257
+ status: results.some((result) => result.status === 'failed') ? 'failed' : 'completed',
1258
+ };
1259
+ plan.execution_summary = executionSummary;
1260
+ plan.planner_summary = {
1261
+ status: 'executed',
1262
+ message: 'Scheduled downstream builders were executed and invocation artifacts were recorded.',
1263
+ };
1264
+ return { results, executionSummary };
1265
+ }
1266
+ function loadInvocationResults(invocationsDir) {
1267
+ if (!existsSync(invocationsDir)) {
1268
+ return [];
1269
+ }
1270
+ return readdirSync(invocationsDir)
1271
+ .filter((entry) => entry.endsWith('.json'))
1272
+ .sort()
1273
+ .map((entry) => readJsonArtifact(path.join(invocationsDir, entry)))
1274
+ .filter((entry) => isRecord(entry))
1275
+ .map((entry) => entry);
1276
+ }
1277
+ function collectPublishBundle(runDir, plan, graphManifest, lineageManifest, executionResults, publishLifecyclemodels, publishResultingProcessRelations) {
1278
+ const lifecyclemodels = [];
1279
+ const projectedProcesses = [];
1280
+ const relations = [];
1281
+ const processBuildRuns = [];
1282
+ executionResults.forEach((result) => {
1283
+ if (result.kind === 'lifecyclemodel_builder' && publishLifecyclemodels) {
1284
+ ensureList(result.artifacts?.produced_model_files).forEach((filePath) => {
1285
+ const resolved = nonEmptyString(filePath);
1286
+ if (!resolved || !existsSync(resolved)) {
1287
+ return;
1288
+ }
1289
+ lifecyclemodels.push({
1290
+ node_id: result.node_id,
1291
+ file: resolved,
1292
+ json_ordered: readJsonArtifact(resolved),
1293
+ });
1294
+ });
1295
+ }
1296
+ if (result.kind === 'projector' && publishResultingProcessRelations) {
1297
+ const bundlePath = nonEmptyString(result.artifacts?.projection_bundle);
1298
+ if (bundlePath && existsSync(bundlePath)) {
1299
+ const bundle = readJsonArtifact(bundlePath);
1300
+ if (isRecord(bundle)) {
1301
+ ensureList(bundle.projected_processes).forEach((payload) => {
1302
+ if (!isRecord(payload)) {
1303
+ return;
1304
+ }
1305
+ projectedProcesses.push({
1306
+ ...copyJson(payload),
1307
+ node_id: result.node_id,
1308
+ });
1309
+ });
1310
+ ensureList(bundle.relations).forEach((payload) => {
1311
+ if (!isRecord(payload)) {
1312
+ return;
1313
+ }
1314
+ relations.push({
1315
+ ...copyJson(payload),
1316
+ node_id: result.node_id,
1317
+ });
1318
+ });
1319
+ }
1320
+ }
1321
+ }
1322
+ if (result.kind === 'process_builder') {
1323
+ processBuildRuns.push({
1324
+ node_id: result.node_id,
1325
+ run_id: nonEmptyString(result.artifacts?.run_id),
1326
+ run_root: nonEmptyString(result.artifacts?.run_root),
1327
+ exports_dir: nonEmptyString(result.artifacts?.exports_dir),
1328
+ });
1329
+ }
1330
+ });
1331
+ return {
1332
+ generated_at: nowIso(),
1333
+ run_dir: runDir,
1334
+ request_id: plan.request_id,
1335
+ status: 'prepared_local_publish_bundle',
1336
+ include_lifecyclemodels: publishLifecyclemodels,
1337
+ include_resulting_process_relations: publishResultingProcessRelations,
1338
+ graph_manifest: copyJson(graphManifest),
1339
+ lineage_manifest: copyJson(lineageManifest),
1340
+ lifecyclemodels,
1341
+ projected_processes: projectedProcesses,
1342
+ resulting_process_relations: relations,
1343
+ process_build_runs: processBuildRuns,
1344
+ };
1345
+ }
1346
+ function normalizeRequestForArtifacts(plan) {
1347
+ return {
1348
+ request_id: plan.request_id,
1349
+ goal: copyJson(plan.goal),
1350
+ root: copyJson(plan.root),
1351
+ orchestration: copyJson(plan.orchestration),
1352
+ candidate_sources: copyJson(plan.candidate_sources),
1353
+ publish: copyJson(plan.publish),
1354
+ nodes: copyJson(plan.nodes),
1355
+ edges: copyJson(plan.edges),
1356
+ notes: copyJson(plan.notes),
1357
+ };
1358
+ }
1359
+ function requireActionInputPath(options) {
1360
+ const inputPath = nonEmptyString(options.inputPath);
1361
+ if (!inputPath) {
1362
+ throw new CliError('Missing required --input for lifecyclemodel orchestrate plan/execute.', {
1363
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_INPUT_REQUIRED',
1364
+ exitCode: 2,
1365
+ });
1366
+ }
1367
+ return path.resolve(inputPath);
1368
+ }
1369
+ function requireActionOutDir(options) {
1370
+ const outDir = nonEmptyString(options.outDir);
1371
+ if (!outDir) {
1372
+ throw new CliError('Missing required --out-dir for lifecyclemodel orchestrate plan/execute.', {
1373
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_OUT_DIR_REQUIRED',
1374
+ exitCode: 2,
1375
+ });
1376
+ }
1377
+ return path.resolve(outDir);
1378
+ }
1379
+ function requirePublishRunDir(options) {
1380
+ const runDir = nonEmptyString(options.runDir);
1381
+ if (!runDir) {
1382
+ throw new CliError('Missing required --run-dir for lifecyclemodel orchestrate publish.', {
1383
+ code: 'LIFECYCLEMODEL_ORCHESTRATE_RUN_DIR_REQUIRED',
1384
+ exitCode: 2,
1385
+ });
1386
+ }
1387
+ return path.resolve(runDir);
1388
+ }
1389
+ export async function runLifecyclemodelOrchestrate(options) {
1390
+ const now = options.now ?? new Date();
1391
+ if (options.action === 'publish') {
1392
+ const runDir = requirePublishRunDir(options);
1393
+ const assemblyPlanPath = path.join(runDir, 'assembly-plan.json');
1394
+ const graphManifestPath = path.join(runDir, 'graph-manifest.json');
1395
+ const lineageManifestPath = path.join(runDir, 'lineage-manifest.json');
1396
+ requireFile(assemblyPlanPath, 'assembly plan');
1397
+ requireFile(graphManifestPath, 'graph manifest');
1398
+ requireFile(lineageManifestPath, 'lineage manifest');
1399
+ const plan = readJsonArtifact(assemblyPlanPath);
1400
+ const graphManifest = readJsonArtifact(graphManifestPath);
1401
+ const lineageManifest = readJsonArtifact(lineageManifestPath);
1402
+ const publishLifecyclemodels = options.publishLifecyclemodels || plan.publish.prepare_lifecyclemodel_payload !== false;
1403
+ const publishResultingProcessRelations = options.publishResultingProcessRelations ||
1404
+ plan.publish.prepare_resulting_process_payload !== false ||
1405
+ plan.publish.prepare_relation_payload !== false;
1406
+ const executionResults = loadInvocationResults(path.join(runDir, 'invocations'));
1407
+ const publishBundle = collectPublishBundle(runDir, plan, graphManifest, lineageManifest, executionResults, publishLifecyclemodels, publishResultingProcessRelations);
1408
+ const publishBundlePath = writeJsonArtifact(path.join(runDir, 'publish-bundle.json'), publishBundle);
1409
+ const publishSummaryPath = writeJsonArtifact(path.join(runDir, 'publish-summary.json'), {
1410
+ schema_version: 1,
1411
+ generated_at_utc: nowIso(now),
1412
+ action: 'publish',
1413
+ status: 'prepared_local_publish_bundle',
1414
+ request_id: plan.request_id,
1415
+ run_dir: runDir,
1416
+ lifecyclemodel_count: ensureList(publishBundle.lifecyclemodels).length,
1417
+ projected_process_count: ensureList(publishBundle.projected_processes).length,
1418
+ relation_count: ensureList(publishBundle.resulting_process_relations).length,
1419
+ process_build_run_count: ensureList(publishBundle.process_build_runs).length,
1420
+ });
1421
+ return {
1422
+ schema_version: 1,
1423
+ generated_at_utc: nowIso(now),
1424
+ action: 'publish',
1425
+ status: 'prepared_local_publish_bundle',
1426
+ request_id: plan.request_id,
1427
+ run_dir: runDir,
1428
+ counts: {
1429
+ lifecyclemodels: ensureList(publishBundle.lifecyclemodels).length,
1430
+ projected_processes: ensureList(publishBundle.projected_processes).length,
1431
+ resulting_process_relations: ensureList(publishBundle.resulting_process_relations).length,
1432
+ process_build_runs: ensureList(publishBundle.process_build_runs).length,
1433
+ },
1434
+ files: {
1435
+ assembly_plan: assemblyPlanPath,
1436
+ graph_manifest: graphManifestPath,
1437
+ lineage_manifest: lineageManifestPath,
1438
+ publish_bundle: publishBundlePath,
1439
+ publish_summary: publishSummaryPath,
1440
+ },
1441
+ };
1442
+ }
1443
+ const inputPath = requireActionInputPath(options);
1444
+ const outDir = requireActionOutDir(options);
1445
+ const request = readJsonInput(inputPath);
1446
+ const requestObject = requireObject(request, 'request');
1447
+ if (options.allowProcessBuild === true) {
1448
+ requestObject.orchestration.allow_process_build = true;
1449
+ }
1450
+ if (options.allowSubmodelBuild === true) {
1451
+ requestObject.orchestration.allow_submodel_build = true;
1452
+ }
1453
+ const plan = buildPlan(requestObject, inputPath, outDir, now);
1454
+ const normalizedRequest = normalizeRequestForArtifacts(plan);
1455
+ const initialGraphManifest = buildGraphManifest(plan, []);
1456
+ const initialLineageManifest = buildLineageManifest(plan, []);
1457
+ const initialBoundaryReport = buildBoundaryReport(plan, []);
1458
+ writePlanArtifacts(normalizedRequest, plan, initialGraphManifest, initialLineageManifest, initialBoundaryReport);
1459
+ if (options.action === 'plan') {
1460
+ return {
1461
+ schema_version: 1,
1462
+ generated_at_utc: nowIso(now),
1463
+ action: 'plan',
1464
+ status: 'planned',
1465
+ request_id: plan.request_id,
1466
+ out_dir: outDir,
1467
+ counts: {
1468
+ nodes: plan.summary.node_count,
1469
+ edges: plan.summary.edge_count,
1470
+ invocations: plan.summary.invocation_count,
1471
+ unresolved: plan.summary.unresolved_count,
1472
+ },
1473
+ files: {
1474
+ request_normalized: plan.artifacts.request_normalized,
1475
+ assembly_plan: plan.artifacts.assembly_plan,
1476
+ graph_manifest: plan.artifacts.graph_manifest,
1477
+ lineage_manifest: plan.artifacts.lineage_manifest,
1478
+ boundary_report: plan.artifacts.boundary_report,
1479
+ },
1480
+ warnings: copyJson(plan.warnings),
1481
+ };
1482
+ }
1483
+ const { results: executionResults, executionSummary } = await executePlan(plan, now, options.env ?? process.env, options.fetchImpl);
1484
+ const graphManifest = buildGraphManifest(plan, executionResults);
1485
+ const lineageManifest = buildLineageManifest(plan, executionResults);
1486
+ const boundaryReport = buildBoundaryReport(plan, executionResults);
1487
+ writePlanArtifacts(normalizedRequest, plan, graphManifest, lineageManifest, boundaryReport);
1488
+ return {
1489
+ schema_version: 1,
1490
+ generated_at_utc: nowIso(now),
1491
+ action: 'execute',
1492
+ status: executionSummary.status,
1493
+ request_id: plan.request_id,
1494
+ out_dir: outDir,
1495
+ execution: {
1496
+ successful_invocations: executionSummary.successful_invocations,
1497
+ failed_invocations: executionSummary.failed_invocations,
1498
+ blocked_invocations: executionSummary.blocked_invocations,
1499
+ },
1500
+ files: {
1501
+ request_normalized: plan.artifacts.request_normalized,
1502
+ assembly_plan: plan.artifacts.assembly_plan,
1503
+ graph_manifest: plan.artifacts.graph_manifest,
1504
+ lineage_manifest: plan.artifacts.lineage_manifest,
1505
+ boundary_report: plan.artifacts.boundary_report,
1506
+ invocations_dir: plan.artifacts.invocations_dir,
1507
+ },
1508
+ warnings: copyJson(plan.warnings),
1509
+ };
1510
+ }
1511
+ export const __testInternals = {
1512
+ invariant,
1513
+ safeSlug,
1514
+ requireObject,
1515
+ requireEnum,
1516
+ requireBoolean,
1517
+ requireInteger,
1518
+ resolveInputPath,
1519
+ defaultCandidateSources,
1520
+ entityFromRoot,
1521
+ normalizeCandidate,
1522
+ normalizeCandidateList,
1523
+ normalizeRequestedAction,
1524
+ normalizeDependsOn,
1525
+ normalizePublishConfig,
1526
+ serializeInvocationConfig,
1527
+ normalizeInvocationFailure,
1528
+ normalizeProcessBuilderConfig,
1529
+ normalizeSubmodelBuilderConfig,
1530
+ normalizeProjectorConfig,
1531
+ normalizeNode,
1532
+ deriveRootNode,
1533
+ buildEdges,
1534
+ topoSortNodes,
1535
+ selectResolution,
1536
+ shouldRunProjector,
1537
+ validateRequestShape,
1538
+ buildPlan,
1539
+ executionStatusByNode,
1540
+ buildGraphManifest,
1541
+ buildLineageManifest,
1542
+ buildBoundaryReport,
1543
+ requireFile,
1544
+ parseInlineJson,
1545
+ inferProjectorModelFile,
1546
+ collectProjectorDependencyArtifacts,
1547
+ executeProcessBuilderInvocation,
1548
+ executeLifecyclemodelBuilderInvocation,
1549
+ executeProjectorInvocation,
1550
+ executePlan,
1551
+ collectPublishBundle,
1552
+ normalizeRequestForArtifacts,
1553
+ loadInvocationResults,
1554
+ buildProjectorRequest,
1555
+ };
1556
+ //# sourceMappingURL=lifecyclemodel-orchestrate.js.map