@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.
- package/README.md +368 -0
- package/bin/tiangong.d.ts +1 -0
- package/bin/tiangong.js +32 -0
- package/dist/scripts/assert-full-coverage.js +77 -0
- package/dist/scripts/assert-full-coverage.js.map +1 -0
- package/dist/src/cli.js +2740 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/lib/artifacts.js +86 -0
- package/dist/src/lib/artifacts.js.map +1 -0
- package/dist/src/lib/dotenv.js +36 -0
- package/dist/src/lib/dotenv.js.map +1 -0
- package/dist/src/lib/env.js +139 -0
- package/dist/src/lib/env.js.map +1 -0
- package/dist/src/lib/errors.js +38 -0
- package/dist/src/lib/errors.js.map +1 -0
- package/dist/src/lib/flow-build-alias-map.js +299 -0
- package/dist/src/lib/flow-build-alias-map.js.map +1 -0
- package/dist/src/lib/flow-get.js +81 -0
- package/dist/src/lib/flow-get.js.map +1 -0
- package/dist/src/lib/flow-governance.js +164 -0
- package/dist/src/lib/flow-governance.js.map +1 -0
- package/dist/src/lib/flow-list.js +187 -0
- package/dist/src/lib/flow-list.js.map +1 -0
- package/dist/src/lib/flow-publish-reviewed-data.js +808 -0
- package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -0
- package/dist/src/lib/flow-publish-version.js +541 -0
- package/dist/src/lib/flow-publish-version.js.map +1 -0
- package/dist/src/lib/flow-read.js +310 -0
- package/dist/src/lib/flow-read.js.map +1 -0
- package/dist/src/lib/flow-regen-product.js +1396 -0
- package/dist/src/lib/flow-regen-product.js.map +1 -0
- package/dist/src/lib/flow-remediate.js +821 -0
- package/dist/src/lib/flow-remediate.js.map +1 -0
- package/dist/src/lib/http.js +45 -0
- package/dist/src/lib/http.js.map +1 -0
- package/dist/src/lib/io.js +30 -0
- package/dist/src/lib/io.js.map +1 -0
- package/dist/src/lib/kb-search.js +83 -0
- package/dist/src/lib/kb-search.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-auto-build.js +1328 -0
- package/dist/src/lib/lifecyclemodel-auto-build.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.js +1556 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-publish-build.js +296 -0
- package/dist/src/lib/lifecyclemodel-publish-build.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js +101 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.js +1152 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-validate-build.js +238 -0
- package/dist/src/lib/lifecyclemodel-validate-build.js.map +1 -0
- package/dist/src/lib/llm.js +383 -0
- package/dist/src/lib/llm.js.map +1 -0
- package/dist/src/lib/process-auto-build.js +717 -0
- package/dist/src/lib/process-auto-build.js.map +1 -0
- package/dist/src/lib/process-batch-build.js +409 -0
- package/dist/src/lib/process-batch-build.js.map +1 -0
- package/dist/src/lib/process-get.js +66 -0
- package/dist/src/lib/process-get.js.map +1 -0
- package/dist/src/lib/process-publish-build.js +451 -0
- package/dist/src/lib/process-publish-build.js.map +1 -0
- package/dist/src/lib/process-resume-build.js +364 -0
- package/dist/src/lib/process-resume-build.js.map +1 -0
- package/dist/src/lib/publish.js +562 -0
- package/dist/src/lib/publish.js.map +1 -0
- package/dist/src/lib/remote.js +88 -0
- package/dist/src/lib/remote.js.map +1 -0
- package/dist/src/lib/review-flow.js +1110 -0
- package/dist/src/lib/review-flow.js.map +1 -0
- package/dist/src/lib/review-lifecyclemodel.js +727 -0
- package/dist/src/lib/review-lifecyclemodel.js.map +1 -0
- package/dist/src/lib/review-process.js +702 -0
- package/dist/src/lib/review-process.js.map +1 -0
- package/dist/src/lib/run.js +106 -0
- package/dist/src/lib/run.js.map +1 -0
- package/dist/src/lib/state-lock.js +150 -0
- package/dist/src/lib/state-lock.js.map +1 -0
- package/dist/src/lib/supabase-client.js +269 -0
- package/dist/src/lib/supabase-client.js.map +1 -0
- package/dist/src/lib/supabase-json-ordered-write.js +186 -0
- package/dist/src/lib/supabase-json-ordered-write.js.map +1 -0
- package/dist/src/lib/supabase-rest.js +138 -0
- package/dist/src/lib/supabase-rest.js.map +1 -0
- package/dist/src/lib/supabase-session.js +424 -0
- package/dist/src/lib/supabase-session.js.map +1 -0
- package/dist/src/lib/tidas-sdk-package-validator.js +493 -0
- package/dist/src/lib/tidas-sdk-package-validator.js.map +1 -0
- package/dist/src/lib/unstructured.js +203 -0
- package/dist/src/lib/unstructured.js.map +1 -0
- package/dist/src/lib/user-api-key.js +75 -0
- package/dist/src/lib/user-api-key.js.map +1 -0
- package/dist/src/lib/validation.js +191 -0
- package/dist/src/lib/validation.js.map +1 -0
- package/dist/src/main.js +38 -0
- package/dist/src/main.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,1152 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { writeJsonArtifact } from './artifacts.js';
|
|
6
|
+
import { CliError } from './errors.js';
|
|
7
|
+
import { readJsonInput } from './io.js';
|
|
8
|
+
import { buildRunId, resolveRunLayout } from './run.js';
|
|
9
|
+
import { fetchExactOrLatestProcessRow, normalizeSupabaseProcessPayload, requireSupabaseRestRuntime, } from './supabase-rest.js';
|
|
10
|
+
import { createSupabaseDataRuntime } from './supabase-session.js';
|
|
11
|
+
const DEFAULT_DATASET_VERSION = '00.00.001';
|
|
12
|
+
const EPSILON = 1e-10;
|
|
13
|
+
const REMOTE_PROCESS_LOOKUP_TIMEOUT_MS = 10_000;
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function ensureList(value) {
|
|
18
|
+
if (value === undefined || value === null) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return Array.isArray(value) ? value : [value];
|
|
22
|
+
}
|
|
23
|
+
function copyJson(value) {
|
|
24
|
+
return JSON.parse(JSON.stringify(value));
|
|
25
|
+
}
|
|
26
|
+
function firstNonEmpty(...values) {
|
|
27
|
+
for (const value of values) {
|
|
28
|
+
if (typeof value === 'string' && value.trim()) {
|
|
29
|
+
return value.trim();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function uniqueStrings(values) {
|
|
35
|
+
const unique = [];
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
values.forEach((value) => {
|
|
38
|
+
if (!value) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (seen.has(value)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
seen.add(value);
|
|
45
|
+
unique.push(value);
|
|
46
|
+
});
|
|
47
|
+
return unique;
|
|
48
|
+
}
|
|
49
|
+
function sha256Text(text) {
|
|
50
|
+
return createHash('sha256').update(text).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
function nowIso(now = new Date()) {
|
|
53
|
+
return now.toISOString();
|
|
54
|
+
}
|
|
55
|
+
function requireRemoteProcessLookupRuntime(env) {
|
|
56
|
+
const missing = [];
|
|
57
|
+
if (typeof env.TIANGONG_LCA_API_BASE_URL !== 'string' || !env.TIANGONG_LCA_API_BASE_URL.trim()) {
|
|
58
|
+
missing.push('TIANGONG_LCA_API_BASE_URL');
|
|
59
|
+
}
|
|
60
|
+
if (typeof env.TIANGONG_LCA_API_KEY !== 'string' || !env.TIANGONG_LCA_API_KEY.trim()) {
|
|
61
|
+
missing.push('TIANGONG_LCA_API_KEY');
|
|
62
|
+
}
|
|
63
|
+
if (typeof env.TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY !== 'string' ||
|
|
64
|
+
!env.TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY.trim()) {
|
|
65
|
+
missing.push('TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY');
|
|
66
|
+
}
|
|
67
|
+
if (missing.length > 0) {
|
|
68
|
+
throw new CliError(`Remote process lookup requires ${missing.join(', ')} when process_sources.allow_remote_lookup=true.`, {
|
|
69
|
+
code: 'LIFECYCLEMODEL_REMOTE_LOOKUP_ENV_REQUIRED',
|
|
70
|
+
exitCode: 2,
|
|
71
|
+
details: { missing },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return requireSupabaseRestRuntime(env);
|
|
75
|
+
}
|
|
76
|
+
async function fetchRemoteProcessRecord(options) {
|
|
77
|
+
const lookupKey = `${options.processId}@${options.requestedVersion}`;
|
|
78
|
+
try {
|
|
79
|
+
const lookup = await fetchExactOrLatestProcessRow({
|
|
80
|
+
runtime: options.runtime,
|
|
81
|
+
id: options.processId,
|
|
82
|
+
version: options.requestedVersion,
|
|
83
|
+
timeoutMs: REMOTE_PROCESS_LOOKUP_TIMEOUT_MS,
|
|
84
|
+
fetchImpl: options.fetchImpl,
|
|
85
|
+
fallbackToLatest: true,
|
|
86
|
+
});
|
|
87
|
+
if (!lookup) {
|
|
88
|
+
throw new CliError(`Could not resolve remote process dataset for ${lookupKey}.`, {
|
|
89
|
+
code: 'LIFECYCLEMODEL_REMOTE_PROCESS_NOT_FOUND',
|
|
90
|
+
exitCode: 2,
|
|
91
|
+
details: { process_id: options.processId, version: options.requestedVersion },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const record = parseProcessRecord(normalizeSupabaseProcessPayload(lookup.row.json, lookupKey), {
|
|
95
|
+
sourceLabel: 'remote:supabase',
|
|
96
|
+
sourcePath: lookup.sourceUrl,
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
record,
|
|
100
|
+
resolution: lookup.resolution === 'remote_supabase_latest_fallback'
|
|
101
|
+
? 'remote_supabase_latest_fallback'
|
|
102
|
+
: 'remote_supabase_exact',
|
|
103
|
+
sourcePath: lookup.sourceUrl,
|
|
104
|
+
resolvedVersion: lookup.row.version || record.version,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (error instanceof CliError && error.code === 'SUPABASE_REST_RESPONSE_INVALID') {
|
|
109
|
+
throw new CliError(`Remote process lookup returned an invalid response for ${lookupKey}.`, {
|
|
110
|
+
code: 'LIFECYCLEMODEL_REMOTE_LOOKUP_RESPONSE_INVALID',
|
|
111
|
+
exitCode: 1,
|
|
112
|
+
details: error.details,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function resolveInputPath(baseDir, value) {
|
|
119
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const trimmed = value.trim();
|
|
123
|
+
if (trimmed.startsWith('file:')) {
|
|
124
|
+
return trimmed;
|
|
125
|
+
}
|
|
126
|
+
if (trimmed.includes('://')) {
|
|
127
|
+
return trimmed;
|
|
128
|
+
}
|
|
129
|
+
return path.resolve(baseDir, trimmed);
|
|
130
|
+
}
|
|
131
|
+
function resolveLocalPath(value, fieldName) {
|
|
132
|
+
if (value.startsWith('file:')) {
|
|
133
|
+
return fileURLToPath(value);
|
|
134
|
+
}
|
|
135
|
+
if (value.includes('://')) {
|
|
136
|
+
throw new CliError(`${fieldName} must resolve to a local filesystem path.`, {
|
|
137
|
+
code: 'LIFECYCLEMODEL_LOCAL_PATH_REQUIRED',
|
|
138
|
+
exitCode: 2,
|
|
139
|
+
details: value,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return path.resolve(value);
|
|
143
|
+
}
|
|
144
|
+
function readJsonObject(filePath, code) {
|
|
145
|
+
const value = readJsonInput(filePath);
|
|
146
|
+
if (!isRecord(value)) {
|
|
147
|
+
throw new CliError(`Expected a JSON object file: ${filePath}`, {
|
|
148
|
+
code,
|
|
149
|
+
exitCode: 2,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
function toFiniteNumber(value, fieldName) {
|
|
155
|
+
if (value === undefined || value === null || value === '') {
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
const parsed = typeof value === 'number' ? value : Number(String(value));
|
|
159
|
+
if (!Number.isFinite(parsed)) {
|
|
160
|
+
throw new CliError(`Expected a numeric value for ${fieldName}.`, {
|
|
161
|
+
code: 'LIFECYCLEMODEL_INVALID_NUMBER',
|
|
162
|
+
exitCode: 2,
|
|
163
|
+
details: { fieldName, value },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return parsed;
|
|
167
|
+
}
|
|
168
|
+
function normalizeNumericOutput(value) {
|
|
169
|
+
const rounded = Math.abs(value) < EPSILON ? 0 : Number.parseFloat(value.toFixed(12));
|
|
170
|
+
return rounded === 0 ? 0 : rounded;
|
|
171
|
+
}
|
|
172
|
+
function toBoolean(value, defaultValue) {
|
|
173
|
+
if (value === undefined) {
|
|
174
|
+
return defaultValue;
|
|
175
|
+
}
|
|
176
|
+
if (typeof value === 'boolean') {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
if (typeof value === 'string') {
|
|
180
|
+
if (value === 'true') {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
if (value === 'false') {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return Boolean(value);
|
|
188
|
+
}
|
|
189
|
+
function normalizeStringArray(value, fieldName) {
|
|
190
|
+
const items = ensureList(value);
|
|
191
|
+
return items.map((item, index) => {
|
|
192
|
+
if (typeof item !== 'string' || !item.trim()) {
|
|
193
|
+
throw new CliError(`Expected ${fieldName}[${index}] to be a non-empty string.`, {
|
|
194
|
+
code: 'LIFECYCLEMODEL_INVALID_STRING_ARRAY',
|
|
195
|
+
exitCode: 2,
|
|
196
|
+
details: { fieldName, value: item },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return item.trim();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function normalizeProjectionMode(value) {
|
|
203
|
+
const mode = firstNonEmpty(value) ?? 'primary-only';
|
|
204
|
+
if (mode === 'primary-only' || mode === 'all-subproducts') {
|
|
205
|
+
return mode;
|
|
206
|
+
}
|
|
207
|
+
throw new CliError('projection.mode must be primary-only or all-subproducts.', {
|
|
208
|
+
code: 'LIFECYCLEMODEL_INVALID_PROJECTION_MODE',
|
|
209
|
+
exitCode: 2,
|
|
210
|
+
details: value,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function normalizePublishIntent(value) {
|
|
214
|
+
const intent = firstNonEmpty(value) ?? 'dry_run';
|
|
215
|
+
if (intent === 'dry_run' || intent === 'prepare_only' || intent === 'publish') {
|
|
216
|
+
return intent;
|
|
217
|
+
}
|
|
218
|
+
throw new CliError('publish.intent must be dry_run, prepare_only, or publish.', {
|
|
219
|
+
code: 'LIFECYCLEMODEL_INVALID_PUBLISH_INTENT',
|
|
220
|
+
exitCode: 2,
|
|
221
|
+
details: value,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function normalizeMetadataOverrides(value) {
|
|
225
|
+
if (value === undefined) {
|
|
226
|
+
return {};
|
|
227
|
+
}
|
|
228
|
+
if (!isRecord(value)) {
|
|
229
|
+
throw new CliError('projection.metadata_overrides must be a JSON object when provided.', {
|
|
230
|
+
code: 'LIFECYCLEMODEL_INVALID_METADATA_OVERRIDES',
|
|
231
|
+
exitCode: 2,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return copyJson(value);
|
|
235
|
+
}
|
|
236
|
+
export function normalizeLifecyclemodelResultingProcessRequest(input, options) {
|
|
237
|
+
if (!isRecord(input)) {
|
|
238
|
+
throw new CliError('Lifecyclemodel resulting-process request must be a JSON object.', {
|
|
239
|
+
code: 'LIFECYCLEMODEL_REQUEST_NOT_OBJECT',
|
|
240
|
+
exitCode: 2,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const baseDir = path.dirname(path.resolve(options.requestPath));
|
|
244
|
+
const sourceModelRaw = isRecord(input.source_model) ? input.source_model : {};
|
|
245
|
+
const projectionRaw = isRecord(input.projection) ? input.projection : {};
|
|
246
|
+
const processSourcesRaw = isRecord(input.process_sources) ? input.process_sources : {};
|
|
247
|
+
const publishRaw = isRecord(input.publish) ? input.publish : {};
|
|
248
|
+
const normalized = {
|
|
249
|
+
source_model: {
|
|
250
|
+
id: firstNonEmpty(sourceModelRaw.id),
|
|
251
|
+
version: firstNonEmpty(sourceModelRaw.version),
|
|
252
|
+
name: firstNonEmpty(sourceModelRaw.name),
|
|
253
|
+
json_ordered_path: resolveInputPath(baseDir, sourceModelRaw.json_ordered_path),
|
|
254
|
+
json_ordered: isRecord(sourceModelRaw.json_ordered)
|
|
255
|
+
? copyJson(sourceModelRaw.json_ordered)
|
|
256
|
+
: null,
|
|
257
|
+
},
|
|
258
|
+
projection: {
|
|
259
|
+
mode: normalizeProjectionMode(projectionRaw.mode),
|
|
260
|
+
process_id: firstNonEmpty(projectionRaw.process_id),
|
|
261
|
+
process_version: firstNonEmpty(projectionRaw.process_version),
|
|
262
|
+
metadata_overrides: normalizeMetadataOverrides(projectionRaw.metadata_overrides),
|
|
263
|
+
attach_graph_snapshot: toBoolean(projectionRaw.attach_graph_snapshot, false),
|
|
264
|
+
attach_graph_snapshot_uri: resolveInputPath(baseDir, projectionRaw.attach_graph_snapshot_uri),
|
|
265
|
+
},
|
|
266
|
+
process_sources: {
|
|
267
|
+
process_catalog_path: (() => {
|
|
268
|
+
const resolved = resolveInputPath(baseDir, processSourcesRaw.process_catalog_path);
|
|
269
|
+
return resolved ? resolveLocalPath(resolved, 'process_sources.process_catalog_path') : null;
|
|
270
|
+
})(),
|
|
271
|
+
run_dirs: normalizeStringArray(processSourcesRaw.run_dirs, 'process_sources.run_dirs').map((item) => resolveLocalPath(resolveInputPath(baseDir, item), 'process_sources.run_dirs')),
|
|
272
|
+
process_json_dirs: normalizeStringArray(processSourcesRaw.process_json_dirs, 'process_sources.process_json_dirs').map((item) => resolveLocalPath(resolveInputPath(baseDir, item), 'process_sources.process_json_dirs')),
|
|
273
|
+
process_json_files: normalizeStringArray(processSourcesRaw.process_json_files, 'process_sources.process_json_files').map((item) => resolveLocalPath(resolveInputPath(baseDir, item), 'process_sources.process_json_files')),
|
|
274
|
+
allow_remote_lookup: toBoolean(processSourcesRaw.allow_remote_lookup ?? processSourcesRaw.allow_mcp_lookup, false),
|
|
275
|
+
},
|
|
276
|
+
publish: {
|
|
277
|
+
intent: normalizePublishIntent(publishRaw.intent),
|
|
278
|
+
prepare_process_payloads: toBoolean(publishRaw.prepare_process_payloads, true),
|
|
279
|
+
prepare_relation_payloads: toBoolean(publishRaw.prepare_relation_payloads, true),
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
if (!normalized.source_model.id &&
|
|
283
|
+
!normalized.source_model.json_ordered &&
|
|
284
|
+
!normalized.source_model.json_ordered_path) {
|
|
285
|
+
throw new CliError('source_model must include at least one of id, json_ordered, or json_ordered_path.', {
|
|
286
|
+
code: 'LIFECYCLEMODEL_SOURCE_MODEL_REQUIRED',
|
|
287
|
+
exitCode: 2,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (!normalized.process_sources.process_catalog_path) {
|
|
291
|
+
normalized.process_sources.process_catalog_path = autoDetectProcessCatalogPath(normalized.source_model.json_ordered_path);
|
|
292
|
+
}
|
|
293
|
+
if (normalized.process_sources.process_json_dirs.length === 0) {
|
|
294
|
+
normalized.process_sources.process_json_dirs = autoDetectProcessJsonDirs(normalized.source_model.json_ordered_path);
|
|
295
|
+
}
|
|
296
|
+
return normalized;
|
|
297
|
+
}
|
|
298
|
+
function lifecyclemodelRoot(model) {
|
|
299
|
+
return isRecord(model.lifeCycleModelDataSet)
|
|
300
|
+
? model.lifeCycleModelDataSet
|
|
301
|
+
: model;
|
|
302
|
+
}
|
|
303
|
+
function processDatasetRoot(payload) {
|
|
304
|
+
if (isRecord(payload.processDataSet)) {
|
|
305
|
+
return payload.processDataSet;
|
|
306
|
+
}
|
|
307
|
+
for (const key of ['json_ordered', 'json']) {
|
|
308
|
+
const nested = payload[key];
|
|
309
|
+
if (isRecord(nested) && isRecord(nested.processDataSet)) {
|
|
310
|
+
return nested.processDataSet;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
throw new CliError('Process payload does not contain processDataSet.', {
|
|
314
|
+
code: 'LIFECYCLEMODEL_PROCESS_DATASET_REQUIRED',
|
|
315
|
+
exitCode: 2,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function resolveNameField(namePayload) {
|
|
319
|
+
if (typeof namePayload === 'string') {
|
|
320
|
+
return namePayload.trim() || null;
|
|
321
|
+
}
|
|
322
|
+
if (Array.isArray(namePayload)) {
|
|
323
|
+
for (const item of namePayload) {
|
|
324
|
+
const resolved = resolveNameField(item);
|
|
325
|
+
if (resolved) {
|
|
326
|
+
return resolved;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
if (!isRecord(namePayload)) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const direct = firstNonEmpty(namePayload['@index'], namePayload['#text'], namePayload.text);
|
|
335
|
+
if (direct) {
|
|
336
|
+
return direct;
|
|
337
|
+
}
|
|
338
|
+
for (const key of ['baseName', 'shortName', 'name']) {
|
|
339
|
+
const nested = namePayload[key];
|
|
340
|
+
if (isRecord(nested)) {
|
|
341
|
+
const nestedText = firstNonEmpty(nested['@index'], nested['#text'], nested.text);
|
|
342
|
+
if (nestedText) {
|
|
343
|
+
return nestedText;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const resolved = firstNonEmpty(nested);
|
|
347
|
+
if (resolved) {
|
|
348
|
+
return resolved;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
function normalizedNameInfo(namePayload, fallbackText) {
|
|
354
|
+
if (isRecord(namePayload) && Object.keys(namePayload).length > 0) {
|
|
355
|
+
return copyJson(namePayload);
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
baseName: [
|
|
359
|
+
{
|
|
360
|
+
'@xml:lang': 'en',
|
|
361
|
+
'#text': resolveNameField(namePayload) ?? fallbackText,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function multilangFromText(enText, zhText) {
|
|
367
|
+
return [
|
|
368
|
+
{
|
|
369
|
+
'@xml:lang': 'en',
|
|
370
|
+
'#text': enText,
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
'@xml:lang': 'zh',
|
|
374
|
+
'#text': zhText,
|
|
375
|
+
},
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
function modelDatasetVersion(model) {
|
|
379
|
+
const root = lifecyclemodelRoot(model);
|
|
380
|
+
const administrative = isRecord(root.administrativeInformation)
|
|
381
|
+
? root.administrativeInformation
|
|
382
|
+
: {};
|
|
383
|
+
const publication = isRecord(administrative.publicationAndOwnership)
|
|
384
|
+
? administrative.publicationAndOwnership
|
|
385
|
+
: isRecord(administrative['common:publicationAndOwnership'])
|
|
386
|
+
? administrative['common:publicationAndOwnership']
|
|
387
|
+
: {};
|
|
388
|
+
return firstNonEmpty(publication['common:dataSetVersion']);
|
|
389
|
+
}
|
|
390
|
+
function modelIdentifier(model, sourceModel) {
|
|
391
|
+
const root = lifecyclemodelRoot(model);
|
|
392
|
+
const info = isRecord(root.lifeCycleModelInformation) ? root.lifeCycleModelInformation : {};
|
|
393
|
+
const dataInfo = isRecord(info.dataSetInformation) ? info.dataSetInformation : {};
|
|
394
|
+
const modelId = firstNonEmpty(sourceModel.id, dataInfo['common:UUID'], root['@id'], root.id, dataInfo.identifierOfSubDataSet) ?? `lm-${sha256Text(JSON.stringify(model)).slice(0, 12)}`;
|
|
395
|
+
const version = firstNonEmpty(sourceModel.version, modelDatasetVersion(model), root['@version'], root.version, dataInfo['@version']) ?? DEFAULT_DATASET_VERSION;
|
|
396
|
+
const name = firstNonEmpty(sourceModel.name, resolveNameField(dataInfo.name), root.name) ?? modelId;
|
|
397
|
+
return {
|
|
398
|
+
id: modelId,
|
|
399
|
+
version,
|
|
400
|
+
name,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function extractProcessInstances(model) {
|
|
404
|
+
const root = lifecyclemodelRoot(model);
|
|
405
|
+
const info = isRecord(root.lifeCycleModelInformation) ? root.lifeCycleModelInformation : {};
|
|
406
|
+
const technology = isRecord(info.technology)
|
|
407
|
+
? info.technology
|
|
408
|
+
: isRecord(root.technology)
|
|
409
|
+
? root.technology
|
|
410
|
+
: {};
|
|
411
|
+
const processes = isRecord(technology.processes) ? technology.processes : {};
|
|
412
|
+
const instances = ensureList(processes.processInstance);
|
|
413
|
+
return instances.filter(isRecord).map((item, index) => {
|
|
414
|
+
const ref = isRecord(item.referenceToProcess) ? item.referenceToProcess : {};
|
|
415
|
+
return {
|
|
416
|
+
instance_id: firstNonEmpty(item['@dataSetInternalID'], item['@id'], item.id) ?? `pi-${index + 1}`,
|
|
417
|
+
process_id: firstNonEmpty(ref['@refObjectId'], ref.id, ref.processId) ?? `proc-${index + 1}`,
|
|
418
|
+
process_version: firstNonEmpty(ref['@version'], ref.version) ?? DEFAULT_DATASET_VERSION,
|
|
419
|
+
label: firstNonEmpty(resolveNameField(ref['common:shortDescription']), resolveNameField(ref.shortDescription), resolveNameField(ref.name), ref['@refObjectId']) ?? `process-${index + 1}`,
|
|
420
|
+
multiplication_factor: normalizeNumericOutput(toFiniteNumber(item['@multiplicationFactor'], 'processInstance.@multiplicationFactor')),
|
|
421
|
+
reference_to_process: copyJson(ref),
|
|
422
|
+
raw: copyJson(item),
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
function extractEdges(model) {
|
|
427
|
+
const edges = [];
|
|
428
|
+
extractProcessInstances(model).forEach((instance) => {
|
|
429
|
+
const connections = isRecord(instance.raw.connections) ? instance.raw.connections : {};
|
|
430
|
+
const outputs = ensureList(connections.outputExchange);
|
|
431
|
+
outputs.filter(isRecord).forEach((exchange, edgeIndex) => {
|
|
432
|
+
const downstreamItems = ensureList(exchange.downstreamProcess).filter(isRecord);
|
|
433
|
+
downstreamItems.forEach((downstream, downstreamIndex) => {
|
|
434
|
+
const downstreamId = firstNonEmpty(downstream['@refObjectId'], downstream['@id'], downstream.id, exchange.downstreamProcessId);
|
|
435
|
+
if (!downstreamId) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
edges.push({
|
|
439
|
+
edge_id: firstNonEmpty(exchange['@id'], exchange.id) ??
|
|
440
|
+
`${instance.instance_id}-edge-${edgeIndex + 1}-${downstreamIndex + 1}`,
|
|
441
|
+
from: instance.instance_id,
|
|
442
|
+
to: downstreamId,
|
|
443
|
+
exchange_id: firstNonEmpty(exchange['@id'], exchange.id),
|
|
444
|
+
flow_uuid: firstNonEmpty(exchange['@flowUUID'], downstream['@flowUUID'], exchange.flowUUID),
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
return edges;
|
|
450
|
+
}
|
|
451
|
+
function processReferencePairs(model) {
|
|
452
|
+
const seen = new Set();
|
|
453
|
+
const pairs = [];
|
|
454
|
+
extractProcessInstances(model).forEach((instance) => {
|
|
455
|
+
const key = `${instance.process_id}@${instance.process_version}`;
|
|
456
|
+
if (seen.has(key)) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
seen.add(key);
|
|
460
|
+
pairs.push({
|
|
461
|
+
process_id: instance.process_id,
|
|
462
|
+
version: instance.process_version,
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
return pairs;
|
|
466
|
+
}
|
|
467
|
+
function parseProcessRecord(payload, options) {
|
|
468
|
+
const dataset = processDatasetRoot(payload);
|
|
469
|
+
const info = isRecord(dataset.processInformation) ? dataset.processInformation : {};
|
|
470
|
+
const dataInfo = isRecord(info.dataSetInformation) ? info.dataSetInformation : {};
|
|
471
|
+
const administrative = isRecord(dataset.administrativeInformation)
|
|
472
|
+
? dataset.administrativeInformation
|
|
473
|
+
: {};
|
|
474
|
+
const publication = isRecord(administrative.publicationAndOwnership)
|
|
475
|
+
? administrative.publicationAndOwnership
|
|
476
|
+
: {};
|
|
477
|
+
const processUuid = firstNonEmpty(dataInfo['common:UUID']);
|
|
478
|
+
if (!processUuid) {
|
|
479
|
+
throw new CliError(`processDataSet missing common:UUID from ${options.sourceLabel}`, {
|
|
480
|
+
code: 'LIFECYCLEMODEL_PROCESS_UUID_REQUIRED',
|
|
481
|
+
exitCode: 2,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
const version = firstNonEmpty(publication['common:dataSetVersion']) ?? DEFAULT_DATASET_VERSION;
|
|
485
|
+
const quantitativeReference = isRecord(info.quantitativeReference)
|
|
486
|
+
? info.quantitativeReference
|
|
487
|
+
: {};
|
|
488
|
+
const referenceInternalId = firstNonEmpty(quantitativeReference.referenceToReferenceFlow);
|
|
489
|
+
const exchangesWrapper = isRecord(dataset.exchanges) ? dataset.exchanges : {};
|
|
490
|
+
const exchanges = ensureList(exchangesWrapper.exchange).filter(isRecord);
|
|
491
|
+
const referenceExchange = exchanges.find((item) => String(item['@dataSetInternalID'] ?? '') === String(referenceInternalId ?? ''));
|
|
492
|
+
if (!referenceExchange) {
|
|
493
|
+
throw new CliError(`reference exchange ${referenceInternalId ?? '(missing)'} not found for process ${processUuid}`, {
|
|
494
|
+
code: 'LIFECYCLEMODEL_REFERENCE_EXCHANGE_NOT_FOUND',
|
|
495
|
+
exitCode: 2,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
const inputAmounts = {};
|
|
499
|
+
const outputAmounts = {};
|
|
500
|
+
exchanges.forEach((exchange) => {
|
|
501
|
+
const flowRef = isRecord(exchange.referenceToFlowDataSet)
|
|
502
|
+
? exchange.referenceToFlowDataSet
|
|
503
|
+
: {};
|
|
504
|
+
const flowUuid = firstNonEmpty(flowRef['@refObjectId']);
|
|
505
|
+
const direction = firstNonEmpty(exchange.exchangeDirection) ?? '';
|
|
506
|
+
if (!flowUuid) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const amount = normalizeNumericOutput(toFiniteNumber(exchange.meanAmount ?? exchange.resultingAmount, 'exchange amount'));
|
|
510
|
+
if (direction === 'Input') {
|
|
511
|
+
inputAmounts[flowUuid] = normalizeNumericOutput((inputAmounts[flowUuid] ?? 0) + amount);
|
|
512
|
+
}
|
|
513
|
+
else if (direction === 'Output') {
|
|
514
|
+
outputAmounts[flowUuid] = normalizeNumericOutput((outputAmounts[flowUuid] ?? 0) + amount);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
const referenceFlow = isRecord(referenceExchange.referenceToFlowDataSet)
|
|
518
|
+
? referenceExchange.referenceToFlowDataSet
|
|
519
|
+
: {};
|
|
520
|
+
return {
|
|
521
|
+
processUuid,
|
|
522
|
+
version,
|
|
523
|
+
raw: {
|
|
524
|
+
processDataSet: dataset,
|
|
525
|
+
},
|
|
526
|
+
sourceLabel: options.sourceLabel,
|
|
527
|
+
sourcePath: options.sourcePath,
|
|
528
|
+
referenceExchangeInternalId: String(referenceInternalId ?? ''),
|
|
529
|
+
referenceFlowUuid: firstNonEmpty(referenceFlow['@refObjectId']) ?? '',
|
|
530
|
+
referenceDirection: firstNonEmpty(referenceExchange.exchangeDirection) ?? '',
|
|
531
|
+
referenceAmount: normalizeNumericOutput(toFiniteNumber(referenceExchange.meanAmount ?? referenceExchange.resultingAmount, 'reference amount')),
|
|
532
|
+
inputAmounts,
|
|
533
|
+
outputAmounts,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function autoDetectProcessCatalogPath(modelPath) {
|
|
537
|
+
if (!modelPath) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
const resolved = PathSafe.resolve(modelPath);
|
|
541
|
+
if (!resolved) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
if (path.basename(path.dirname(resolved)) !== 'lifecyclemodels') {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
const tidasBundleDir = path.dirname(path.dirname(resolved));
|
|
548
|
+
if (path.basename(tidasBundleDir) !== 'tidas_bundle') {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
const candidate = path.join(path.dirname(tidasBundleDir), 'process-catalog.json');
|
|
552
|
+
return existsSync(candidate) ? candidate : null;
|
|
553
|
+
}
|
|
554
|
+
function autoDetectProcessJsonDirs(modelPath) {
|
|
555
|
+
if (!modelPath) {
|
|
556
|
+
return [];
|
|
557
|
+
}
|
|
558
|
+
const resolved = PathSafe.resolve(modelPath);
|
|
559
|
+
if (!resolved) {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
const candidates = [path.join(path.dirname(resolved), 'processes')];
|
|
563
|
+
if (path.basename(path.dirname(resolved)) === 'lifecyclemodels') {
|
|
564
|
+
candidates.push(path.join(path.dirname(path.dirname(resolved)), 'processes'));
|
|
565
|
+
}
|
|
566
|
+
const stem = path.basename(resolved, path.extname(resolved));
|
|
567
|
+
if (stem.endsWith('-model')) {
|
|
568
|
+
const prefix = stem.slice(0, -'-model'.length).trim();
|
|
569
|
+
if (prefix) {
|
|
570
|
+
candidates.push(path.join(path.dirname(resolved), `${prefix}-processes`));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (stem.endsWith('_model')) {
|
|
574
|
+
const prefix = stem.slice(0, -'_model'.length).trim();
|
|
575
|
+
if (prefix) {
|
|
576
|
+
candidates.push(path.join(path.dirname(resolved), `${prefix}_processes`));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return uniqueStrings(candidates.filter((candidate) => existsSync(candidate)));
|
|
580
|
+
}
|
|
581
|
+
const PathSafe = {
|
|
582
|
+
resolve(value) {
|
|
583
|
+
try {
|
|
584
|
+
return resolveLocalPath(value, 'path');
|
|
585
|
+
}
|
|
586
|
+
catch {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
function processSourceDirs(request) {
|
|
592
|
+
const dirs = [...request.process_sources.process_json_dirs];
|
|
593
|
+
request.process_sources.run_dirs.forEach((runDir) => {
|
|
594
|
+
const candidate = path.join(runDir, 'exports', 'processes');
|
|
595
|
+
if (existsSync(candidate)) {
|
|
596
|
+
dirs.push(candidate);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
if (request.process_sources.process_catalog_path) {
|
|
600
|
+
try {
|
|
601
|
+
const payload = JSON.parse(readFileSync(request.process_sources.process_catalog_path, 'utf8'));
|
|
602
|
+
ensureList(payload)
|
|
603
|
+
.filter(isRecord)
|
|
604
|
+
.forEach((item) => {
|
|
605
|
+
const sourceLabel = firstNonEmpty(item.source_label);
|
|
606
|
+
if (!sourceLabel) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const candidate = path.join(sourceLabel, 'exports', 'processes');
|
|
610
|
+
if (existsSync(candidate)) {
|
|
611
|
+
dirs.push(candidate);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// Keep local process resolution best-effort for catalog expansion.
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return uniqueStrings(dirs);
|
|
620
|
+
}
|
|
621
|
+
function locateLocalProcessFile(processId, version, options) {
|
|
622
|
+
const targetFileName = `${processId}_${version}.json`;
|
|
623
|
+
for (const directory of options.processDirs) {
|
|
624
|
+
const candidate = path.join(directory, targetFileName);
|
|
625
|
+
if (existsSync(candidate)) {
|
|
626
|
+
return candidate;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
for (const filePath of options.processFiles) {
|
|
630
|
+
if (existsSync(filePath) && path.basename(filePath) === targetFileName) {
|
|
631
|
+
return filePath;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
async function resolveProcessRecords(request, options) {
|
|
637
|
+
const requiredPairs = processReferencePairs(options.sourceModelJson);
|
|
638
|
+
const processFiles = request.process_sources.process_json_files;
|
|
639
|
+
const processDirs = processSourceDirs(request);
|
|
640
|
+
const records = {};
|
|
641
|
+
const resolutionItems = [];
|
|
642
|
+
const unresolved = [];
|
|
643
|
+
requiredPairs.forEach(({ process_id, version }) => {
|
|
644
|
+
const localPath = locateLocalProcessFile(process_id, version, {
|
|
645
|
+
processDirs,
|
|
646
|
+
processFiles,
|
|
647
|
+
});
|
|
648
|
+
if (localPath) {
|
|
649
|
+
const record = parseProcessRecord(readJsonObject(localPath, 'LIFECYCLEMODEL_PROCESS_FILE_NOT_OBJECT'), {
|
|
650
|
+
sourceLabel: path.dirname(localPath),
|
|
651
|
+
sourcePath: localPath,
|
|
652
|
+
});
|
|
653
|
+
records[`${process_id}@${version}`] = record;
|
|
654
|
+
resolutionItems.push({
|
|
655
|
+
process_id,
|
|
656
|
+
version,
|
|
657
|
+
resolution: 'local_file',
|
|
658
|
+
source_path: localPath,
|
|
659
|
+
});
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
unresolved.push({ process_id, version });
|
|
663
|
+
});
|
|
664
|
+
if (unresolved.length > 0 && request.process_sources.allow_remote_lookup) {
|
|
665
|
+
const env = options.remoteLookup?.env ?? process.env;
|
|
666
|
+
const fetchImpl = options.remoteLookup?.fetchImpl ?? fetch;
|
|
667
|
+
const runtime = createSupabaseDataRuntime({
|
|
668
|
+
runtime: requireRemoteProcessLookupRuntime(env),
|
|
669
|
+
fetchImpl,
|
|
670
|
+
timeoutMs: REMOTE_PROCESS_LOOKUP_TIMEOUT_MS,
|
|
671
|
+
});
|
|
672
|
+
for (const item of unresolved) {
|
|
673
|
+
const remoteRecord = await fetchRemoteProcessRecord({
|
|
674
|
+
processId: item.process_id,
|
|
675
|
+
requestedVersion: item.version,
|
|
676
|
+
runtime,
|
|
677
|
+
fetchImpl,
|
|
678
|
+
});
|
|
679
|
+
records[`${item.process_id}@${item.version}`] = remoteRecord.record;
|
|
680
|
+
resolutionItems.push({
|
|
681
|
+
process_id: item.process_id,
|
|
682
|
+
requested_version: item.version,
|
|
683
|
+
resolved_version: remoteRecord.resolvedVersion,
|
|
684
|
+
resolution: remoteRecord.resolution,
|
|
685
|
+
source_path: remoteRecord.sourcePath,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const remainingUnresolved = requiredPairs.filter(({ process_id, version }) => !records[`${process_id}@${version}`]);
|
|
690
|
+
if (remainingUnresolved.length > 0) {
|
|
691
|
+
const missing = remainingUnresolved
|
|
692
|
+
.map((item) => `${item.process_id}@${item.version}`)
|
|
693
|
+
.join(', ');
|
|
694
|
+
throw new CliError(`Could not resolve referenced process datasets for lifecycle model: ${missing}.`, {
|
|
695
|
+
code: 'LIFECYCLEMODEL_PROCESS_RESOLUTION_FAILED',
|
|
696
|
+
exitCode: 2,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
records,
|
|
701
|
+
resolutionSummary: {
|
|
702
|
+
required_process_count: requiredPairs.length,
|
|
703
|
+
resolved_process_count: Object.keys(records).length,
|
|
704
|
+
local_process_dir_count: processDirs.length,
|
|
705
|
+
explicit_process_file_count: processFiles.length,
|
|
706
|
+
remote_resolution_count: resolutionItems.filter((item) => String(item.resolution).startsWith('remote_')).length,
|
|
707
|
+
items: resolutionItems,
|
|
708
|
+
},
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function loadSourceModel(request) {
|
|
712
|
+
if (request.source_model.json_ordered) {
|
|
713
|
+
return {
|
|
714
|
+
sourceModelJson: copyJson(request.source_model.json_ordered),
|
|
715
|
+
modelPath: null,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
if (!request.source_model.json_ordered_path) {
|
|
719
|
+
throw new CliError('source_model must include json_ordered or json_ordered_path.', {
|
|
720
|
+
code: 'LIFECYCLEMODEL_SOURCE_MODEL_PATH_REQUIRED',
|
|
721
|
+
exitCode: 2,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
const localPath = resolveLocalPath(request.source_model.json_ordered_path, 'source_model.json_ordered_path');
|
|
725
|
+
return {
|
|
726
|
+
sourceModelJson: readJsonObject(localPath, 'LIFECYCLEMODEL_SOURCE_MODEL_NOT_OBJECT'),
|
|
727
|
+
modelPath: localPath,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
function referenceToResultingProcess(model) {
|
|
731
|
+
const root = lifecyclemodelRoot(model);
|
|
732
|
+
const info = isRecord(root.lifeCycleModelInformation) ? root.lifeCycleModelInformation : {};
|
|
733
|
+
const dataInfo = isRecord(info.dataSetInformation) ? info.dataSetInformation : {};
|
|
734
|
+
const ref = isRecord(dataInfo.referenceToResultingProcess)
|
|
735
|
+
? dataInfo.referenceToResultingProcess
|
|
736
|
+
: {};
|
|
737
|
+
return {
|
|
738
|
+
id: firstNonEmpty(ref['@refObjectId'], ref.id),
|
|
739
|
+
version: firstNonEmpty(ref['@version'], ref.version),
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function referenceProcessInstanceId(model) {
|
|
743
|
+
const root = lifecyclemodelRoot(model);
|
|
744
|
+
const info = isRecord(root.lifeCycleModelInformation) ? root.lifeCycleModelInformation : {};
|
|
745
|
+
const quantitative = isRecord(info.quantitativeReference) ? info.quantitativeReference : {};
|
|
746
|
+
const ref = quantitative.referenceToReferenceProcess;
|
|
747
|
+
return isRecord(ref) ? firstNonEmpty(ref['@refObjectId'], ref.id) : firstNonEmpty(ref);
|
|
748
|
+
}
|
|
749
|
+
function chooseReferenceInstance(processInstances, requestedInstanceId) {
|
|
750
|
+
const directMatch = requestedInstanceId
|
|
751
|
+
? processInstances.find((item) => item.instance_id === requestedInstanceId)
|
|
752
|
+
: null;
|
|
753
|
+
if (directMatch) {
|
|
754
|
+
return directMatch;
|
|
755
|
+
}
|
|
756
|
+
const scaled = processInstances.find((item) => item.multiplication_factor > 0);
|
|
757
|
+
if (scaled) {
|
|
758
|
+
return scaled;
|
|
759
|
+
}
|
|
760
|
+
const fallback = processInstances[0];
|
|
761
|
+
if (!fallback) {
|
|
762
|
+
throw new CliError('Lifecycle model does not contain any process instances.', {
|
|
763
|
+
code: 'LIFECYCLEMODEL_PROCESS_INSTANCES_REQUIRED',
|
|
764
|
+
exitCode: 2,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
return fallback;
|
|
768
|
+
}
|
|
769
|
+
function cloneExchangeWithAmount(exchange, amount, internalId, options) {
|
|
770
|
+
const cloned = copyJson(exchange);
|
|
771
|
+
cloned['@dataSetInternalID'] = internalId;
|
|
772
|
+
const normalizedAmount = normalizeNumericOutput(amount);
|
|
773
|
+
cloned.meanAmount = normalizedAmount;
|
|
774
|
+
if ('resultingAmount' in cloned) {
|
|
775
|
+
cloned.resultingAmount = normalizedAmount;
|
|
776
|
+
}
|
|
777
|
+
cloned.quantitativeReference = options.quantitativeReference;
|
|
778
|
+
return cloned;
|
|
779
|
+
}
|
|
780
|
+
function buildResultingProcessPayload(options) {
|
|
781
|
+
const chosenInstance = chooseReferenceInstance(options.processInstances, options.referenceProcessInstanceId);
|
|
782
|
+
const chosenKey = `${chosenInstance.process_id}@${chosenInstance.process_version}`;
|
|
783
|
+
const finalRecord = options.processRecords[chosenKey];
|
|
784
|
+
const finalProcess = copyJson(finalRecord.raw);
|
|
785
|
+
const finalDataset = processDatasetRoot(finalProcess);
|
|
786
|
+
const totals = new Map();
|
|
787
|
+
options.processInstances.forEach((instance) => {
|
|
788
|
+
if (instance.multiplication_factor === 0) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const record = options.processRecords[`${instance.process_id}@${instance.process_version}`];
|
|
792
|
+
const recordDataset = processDatasetRoot(record.raw);
|
|
793
|
+
const exchangesWrapper = isRecord(recordDataset.exchanges)
|
|
794
|
+
? recordDataset.exchanges
|
|
795
|
+
: {};
|
|
796
|
+
const exchanges = ensureList(exchangesWrapper.exchange).filter(isRecord);
|
|
797
|
+
exchanges.forEach((exchange) => {
|
|
798
|
+
const flowRef = isRecord(exchange.referenceToFlowDataSet)
|
|
799
|
+
? exchange.referenceToFlowDataSet
|
|
800
|
+
: {};
|
|
801
|
+
const flowUuid = firstNonEmpty(flowRef['@refObjectId']);
|
|
802
|
+
const direction = firstNonEmpty(exchange.exchangeDirection);
|
|
803
|
+
if (!flowUuid || (direction !== 'Input' && direction !== 'Output')) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const key = `${flowUuid}\u0000${direction}`;
|
|
807
|
+
const scaledAmount = toFiniteNumber(exchange.meanAmount ?? exchange.resultingAmount, 'exchange amount') *
|
|
808
|
+
instance.multiplication_factor;
|
|
809
|
+
const existing = totals.get(key);
|
|
810
|
+
if (existing) {
|
|
811
|
+
existing.amount = normalizeNumericOutput(existing.amount + scaledAmount);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
totals.set(key, {
|
|
815
|
+
amount: normalizeNumericOutput(scaledAmount),
|
|
816
|
+
exchange: copyJson(exchange),
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
const instanceById = new Map(options.processInstances.map((item) => [item.instance_id, item]));
|
|
821
|
+
options.edges.forEach((edge) => {
|
|
822
|
+
if (!edge.flow_uuid) {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const downstreamInstance = instanceById.get(edge.to);
|
|
826
|
+
if (!downstreamInstance) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const downstreamRecord = options.processRecords[`${downstreamInstance.process_id}@${downstreamInstance.process_version}`];
|
|
830
|
+
if (!downstreamRecord) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const internalAmount = (downstreamRecord.inputAmounts[edge.flow_uuid] ?? 0) *
|
|
834
|
+
downstreamInstance.multiplication_factor;
|
|
835
|
+
['Output', 'Input'].forEach((direction) => {
|
|
836
|
+
const key = `${edge.flow_uuid}\u0000${direction}`;
|
|
837
|
+
const existing = totals.get(key);
|
|
838
|
+
if (!existing) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
existing.amount = normalizeNumericOutput(existing.amount - internalAmount);
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
const exchangeItems = [];
|
|
845
|
+
let nextInternalId = 1;
|
|
846
|
+
let referenceExchangeInternalId = '';
|
|
847
|
+
const sortedEntries = [...totals.entries()].sort((left, right) => left[0].localeCompare(right[0]));
|
|
848
|
+
sortedEntries.forEach(([key, payload]) => {
|
|
849
|
+
if (payload.amount <= 0) {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const [flowUuid, direction] = key.split('\u0000');
|
|
853
|
+
const quantitativeReference = flowUuid === finalRecord.referenceFlowUuid &&
|
|
854
|
+
direction === (finalRecord.referenceDirection || 'Output') &&
|
|
855
|
+
!referenceExchangeInternalId;
|
|
856
|
+
const internalId = String(nextInternalId);
|
|
857
|
+
nextInternalId += 1;
|
|
858
|
+
if (quantitativeReference) {
|
|
859
|
+
referenceExchangeInternalId = internalId;
|
|
860
|
+
}
|
|
861
|
+
exchangeItems.push(cloneExchangeWithAmount(payload.exchange, payload.amount, internalId, {
|
|
862
|
+
quantitativeReference,
|
|
863
|
+
}));
|
|
864
|
+
});
|
|
865
|
+
if (!referenceExchangeInternalId) {
|
|
866
|
+
throw new CliError(`Could not build resulting process reference exchange for lifecycle model ${options.sourceModelId}.`, {
|
|
867
|
+
code: 'LIFECYCLEMODEL_REFERENCE_OUTPUT_REQUIRED',
|
|
868
|
+
exitCode: 2,
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
const processInformation = isRecord(finalDataset.processInformation)
|
|
872
|
+
? finalDataset.processInformation
|
|
873
|
+
: {};
|
|
874
|
+
finalDataset.processInformation = processInformation;
|
|
875
|
+
const dataInfo = isRecord(processInformation.dataSetInformation)
|
|
876
|
+
? processInformation.dataSetInformation
|
|
877
|
+
: {};
|
|
878
|
+
processInformation.dataSetInformation = dataInfo;
|
|
879
|
+
dataInfo['common:UUID'] = options.processId;
|
|
880
|
+
dataInfo.name = copyJson(options.sourceModelNameInfo);
|
|
881
|
+
const generalComment = ensureList(dataInfo['common:generalComment']).filter(isRecord);
|
|
882
|
+
generalComment.push(...multilangFromText(`Local ${options.role} resulting process generated from lifecycle model ${options.sourceModelId}; exchanges are aggregated from included processes with internal linked flows cancelled.`, `本地为生命周期模型 ${options.sourceModelId} 生成的 ${options.role} resulting process;其 exchanges 由包含过程聚合并抵消内部连接 flow 后得到。`));
|
|
883
|
+
dataInfo['common:generalComment'] = generalComment;
|
|
884
|
+
const quantitativeReference = isRecord(processInformation.quantitativeReference)
|
|
885
|
+
? processInformation.quantitativeReference
|
|
886
|
+
: {};
|
|
887
|
+
processInformation.quantitativeReference = quantitativeReference;
|
|
888
|
+
quantitativeReference.referenceToReferenceFlow = referenceExchangeInternalId;
|
|
889
|
+
const technology = isRecord(processInformation.technology)
|
|
890
|
+
? processInformation.technology
|
|
891
|
+
: {};
|
|
892
|
+
processInformation.technology = technology;
|
|
893
|
+
const includedRefs = options.processInstances.map((item) => copyJson(item.reference_to_process));
|
|
894
|
+
if (includedRefs.length > 0) {
|
|
895
|
+
technology.referenceToIncludedProcesses =
|
|
896
|
+
includedRefs.length === 1 ? includedRefs[0] : includedRefs;
|
|
897
|
+
}
|
|
898
|
+
finalDataset.exchanges = {
|
|
899
|
+
exchange: exchangeItems.length === 1 ? exchangeItems[0] : exchangeItems,
|
|
900
|
+
};
|
|
901
|
+
const administrative = isRecord(finalDataset.administrativeInformation)
|
|
902
|
+
? finalDataset.administrativeInformation
|
|
903
|
+
: {};
|
|
904
|
+
finalDataset.administrativeInformation = administrative;
|
|
905
|
+
const publication = isRecord(administrative.publicationAndOwnership)
|
|
906
|
+
? administrative.publicationAndOwnership
|
|
907
|
+
: {};
|
|
908
|
+
administrative.publicationAndOwnership = publication;
|
|
909
|
+
publication['common:dataSetVersion'] = options.processVersion;
|
|
910
|
+
publication['common:permanentDataSetURI'] =
|
|
911
|
+
`https://local.tiangong.invalid/processes/${options.processId}?version=${options.processVersion}`;
|
|
912
|
+
const modellingAndValidation = isRecord(finalDataset.modellingAndValidation)
|
|
913
|
+
? finalDataset.modellingAndValidation
|
|
914
|
+
: {};
|
|
915
|
+
finalDataset.modellingAndValidation = modellingAndValidation;
|
|
916
|
+
const lciMethod = isRecord(modellingAndValidation.LCIMethodAndAllocation)
|
|
917
|
+
? modellingAndValidation.LCIMethodAndAllocation
|
|
918
|
+
: {};
|
|
919
|
+
modellingAndValidation.LCIMethodAndAllocation = lciMethod;
|
|
920
|
+
const typeOfDataSet = firstNonEmpty(options.metadataOverrides.type_of_data_set) ?? 'partly terminated system';
|
|
921
|
+
lciMethod.typeOfDataSet = typeOfDataSet;
|
|
922
|
+
dataInfo.generatedFromLifecycleModel = {
|
|
923
|
+
id: options.sourceModelId,
|
|
924
|
+
version: options.sourceModelVersion,
|
|
925
|
+
role: options.role,
|
|
926
|
+
};
|
|
927
|
+
const metadata = {
|
|
928
|
+
generated_from_lifecyclemodel_id: options.sourceModelId,
|
|
929
|
+
generated_from_lifecyclemodel_version: options.sourceModelVersion,
|
|
930
|
+
projection_role: options.role,
|
|
931
|
+
projection_signature: options.projectionSignature,
|
|
932
|
+
type_of_data_set: typeOfDataSet,
|
|
933
|
+
...copyJson(options.metadataOverrides),
|
|
934
|
+
};
|
|
935
|
+
if (options.attachGraphSnapshotUri) {
|
|
936
|
+
metadata.graph_snapshot_uri = options.attachGraphSnapshotUri;
|
|
937
|
+
}
|
|
938
|
+
finalProcess.projectionMetadata = metadata;
|
|
939
|
+
finalProcess.topologySummary = {
|
|
940
|
+
process_instance_count: options.processInstances.length,
|
|
941
|
+
edge_count: options.edges.length,
|
|
942
|
+
};
|
|
943
|
+
return finalProcess;
|
|
944
|
+
}
|
|
945
|
+
async function buildProjectionBundle(options) {
|
|
946
|
+
const root = lifecyclemodelRoot(options.sourceModelJson);
|
|
947
|
+
const modelInfo = isRecord(root.lifeCycleModelInformation) ? root.lifeCycleModelInformation : {};
|
|
948
|
+
const modelDataInfo = isRecord(modelInfo.dataSetInformation) ? modelInfo.dataSetInformation : {};
|
|
949
|
+
const modelIdentity = modelIdentifier(options.sourceModelJson, options.request.source_model);
|
|
950
|
+
const sourceModelNameInfo = normalizedNameInfo(modelDataInfo.name, modelIdentity.name);
|
|
951
|
+
const processInstances = extractProcessInstances(options.sourceModelJson);
|
|
952
|
+
const edges = extractEdges(options.sourceModelJson);
|
|
953
|
+
const processResolution = await resolveProcessRecords(options.request, {
|
|
954
|
+
sourceModelJson: options.sourceModelJson,
|
|
955
|
+
remoteLookup: options.remoteLookup,
|
|
956
|
+
});
|
|
957
|
+
const referenceProcess = referenceToResultingProcess(options.sourceModelJson);
|
|
958
|
+
const referenceProcessInstance = referenceProcessInstanceId(options.sourceModelJson);
|
|
959
|
+
const signatureSeed = {
|
|
960
|
+
source_model_id: modelIdentity.id,
|
|
961
|
+
source_model_version: modelIdentity.version,
|
|
962
|
+
projection_mode: options.request.projection.mode,
|
|
963
|
+
process_instances: processInstances.map((item) => ({
|
|
964
|
+
instance_id: item.instance_id,
|
|
965
|
+
process_id: item.process_id,
|
|
966
|
+
process_version: item.process_version,
|
|
967
|
+
})),
|
|
968
|
+
edges,
|
|
969
|
+
};
|
|
970
|
+
const projectionSignature = `sha256:${sha256Text(JSON.stringify(signatureSeed))}`;
|
|
971
|
+
const primaryProcessId = options.request.projection.process_id ??
|
|
972
|
+
referenceProcess.id ??
|
|
973
|
+
`${modelIdentity.id}-resulting-process`;
|
|
974
|
+
const primaryProcessVersion = options.request.projection.process_version ?? referenceProcess.version ?? modelIdentity.version;
|
|
975
|
+
const notes = [
|
|
976
|
+
'This command materializes a deterministic resulting processDataSet by aggregating included process exchanges and cancelling internal linked flows.',
|
|
977
|
+
'Remote writes remain gated behind an explicit publish layer.',
|
|
978
|
+
];
|
|
979
|
+
const primaryPayload = buildResultingProcessPayload({
|
|
980
|
+
sourceModelId: modelIdentity.id,
|
|
981
|
+
sourceModelVersion: modelIdentity.version,
|
|
982
|
+
sourceModelName: modelIdentity.name,
|
|
983
|
+
sourceModelNameInfo,
|
|
984
|
+
processId: primaryProcessId,
|
|
985
|
+
processVersion: primaryProcessVersion,
|
|
986
|
+
role: 'primary',
|
|
987
|
+
projectionSignature,
|
|
988
|
+
processInstances,
|
|
989
|
+
edges,
|
|
990
|
+
processRecords: processResolution.records,
|
|
991
|
+
referenceProcessInstanceId: referenceProcessInstance,
|
|
992
|
+
metadataOverrides: options.request.projection.metadata_overrides,
|
|
993
|
+
attachGraphSnapshotUri: options.request.projection.attach_graph_snapshot_uri,
|
|
994
|
+
});
|
|
995
|
+
if (options.request.projection.mode === 'all-subproducts') {
|
|
996
|
+
const jsonTg = isRecord(root.json_tg) ? root.json_tg : {};
|
|
997
|
+
const submodels = ensureList(jsonTg.submodels).filter(isRecord);
|
|
998
|
+
notes.push(submodels.length > 0
|
|
999
|
+
? 'Subproduct projection was requested, but this lifecycle model only carries submodel metadata and no submodel-specific topology slices; only the primary aggregated resulting process was emitted.'
|
|
1000
|
+
: 'Subproduct projection was requested, but the lifecycle model does not expose submodel topology metadata; only the primary aggregated resulting process was emitted.');
|
|
1001
|
+
}
|
|
1002
|
+
const sourceModelSummary = {
|
|
1003
|
+
id: modelIdentity.id,
|
|
1004
|
+
version: modelIdentity.version,
|
|
1005
|
+
name: modelIdentity.name,
|
|
1006
|
+
json_ordered_path: options.modelPath,
|
|
1007
|
+
reference_to_resulting_process_id: referenceProcess.id,
|
|
1008
|
+
reference_to_resulting_process_version: referenceProcess.version,
|
|
1009
|
+
reference_process_instance_id: referenceProcessInstance,
|
|
1010
|
+
resolved_process_summary: processResolution.resolutionSummary,
|
|
1011
|
+
};
|
|
1012
|
+
const report = {
|
|
1013
|
+
generated_at: nowIso(),
|
|
1014
|
+
status: options.request.publish.intent === 'publish'
|
|
1015
|
+
? 'projected_local_bundle'
|
|
1016
|
+
: 'prepared_local_bundle',
|
|
1017
|
+
source_model: sourceModelSummary,
|
|
1018
|
+
projection_mode: options.request.projection.mode,
|
|
1019
|
+
node_count: processInstances.length,
|
|
1020
|
+
edge_count: edges.length,
|
|
1021
|
+
reference_process_instance_id: referenceProcessInstance,
|
|
1022
|
+
process_instance_preview: processInstances.slice(0, 10).map((item) => ({
|
|
1023
|
+
instance_id: item.instance_id,
|
|
1024
|
+
process_id: item.process_id,
|
|
1025
|
+
label: item.label,
|
|
1026
|
+
})),
|
|
1027
|
+
edge_preview: edges.slice(0, 10),
|
|
1028
|
+
projection_signature: projectionSignature,
|
|
1029
|
+
attach_graph_snapshot_uri: options.request.projection.attach_graph_snapshot_uri,
|
|
1030
|
+
resolved_process_summary: processResolution.resolutionSummary,
|
|
1031
|
+
projected_process_count: 1,
|
|
1032
|
+
notes,
|
|
1033
|
+
};
|
|
1034
|
+
const bundle = {
|
|
1035
|
+
source_model: sourceModelSummary,
|
|
1036
|
+
projected_processes: [
|
|
1037
|
+
{
|
|
1038
|
+
role: 'primary',
|
|
1039
|
+
id: primaryProcessId,
|
|
1040
|
+
version: primaryProcessVersion,
|
|
1041
|
+
name: modelIdentity.name,
|
|
1042
|
+
json_ordered: primaryPayload,
|
|
1043
|
+
metadata: primaryPayload.projectionMetadata,
|
|
1044
|
+
},
|
|
1045
|
+
],
|
|
1046
|
+
relations: [
|
|
1047
|
+
{
|
|
1048
|
+
lifecyclemodel_id: modelIdentity.id,
|
|
1049
|
+
lifecyclemodel_version: modelIdentity.version,
|
|
1050
|
+
resulting_process_id: primaryProcessId,
|
|
1051
|
+
resulting_process_version: primaryProcessVersion,
|
|
1052
|
+
projection_role: 'primary',
|
|
1053
|
+
projection_signature: projectionSignature,
|
|
1054
|
+
is_primary: true,
|
|
1055
|
+
},
|
|
1056
|
+
],
|
|
1057
|
+
report,
|
|
1058
|
+
projection: {
|
|
1059
|
+
mode: options.request.projection.mode,
|
|
1060
|
+
metadata_overrides: options.request.projection.metadata_overrides,
|
|
1061
|
+
attach_graph_snapshot_uri: options.request.projection.attach_graph_snapshot_uri,
|
|
1062
|
+
},
|
|
1063
|
+
};
|
|
1064
|
+
return {
|
|
1065
|
+
bundle,
|
|
1066
|
+
report,
|
|
1067
|
+
sourceModelSummary,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
function defaultOutDir(requestPath, subject, now = new Date()) {
|
|
1071
|
+
const requestDir = path.dirname(path.resolve(requestPath));
|
|
1072
|
+
const runId = buildRunId({
|
|
1073
|
+
namespace: 'lifecyclemodel_resulting_process',
|
|
1074
|
+
subject,
|
|
1075
|
+
operation: 'build',
|
|
1076
|
+
now,
|
|
1077
|
+
});
|
|
1078
|
+
return resolveRunLayout(path.join(requestDir, 'artifacts'), 'lifecyclemodel_resulting_process', runId).runRoot;
|
|
1079
|
+
}
|
|
1080
|
+
export async function runLifecyclemodelBuildResultingProcess(options) {
|
|
1081
|
+
const requestPath = path.resolve(options.inputPath);
|
|
1082
|
+
const input = readJsonInput(requestPath);
|
|
1083
|
+
const normalizedRequest = normalizeLifecyclemodelResultingProcessRequest(input, {
|
|
1084
|
+
requestPath,
|
|
1085
|
+
});
|
|
1086
|
+
const sourceModel = loadSourceModel(normalizedRequest);
|
|
1087
|
+
const modelIdentity = modelIdentifier(sourceModel.sourceModelJson, normalizedRequest.source_model);
|
|
1088
|
+
const outDir = options.outDir
|
|
1089
|
+
? path.resolve(options.outDir)
|
|
1090
|
+
: defaultOutDir(requestPath, modelIdentity.id, options.now);
|
|
1091
|
+
const projection = await buildProjectionBundle({
|
|
1092
|
+
request: normalizedRequest,
|
|
1093
|
+
sourceModelJson: sourceModel.sourceModelJson,
|
|
1094
|
+
modelPath: sourceModel.modelPath,
|
|
1095
|
+
remoteLookup: {
|
|
1096
|
+
env: options.env ?? process.env,
|
|
1097
|
+
fetchImpl: options.fetchImpl ?? fetch,
|
|
1098
|
+
},
|
|
1099
|
+
});
|
|
1100
|
+
const files = {
|
|
1101
|
+
normalized_request: writeJsonArtifact(path.join(outDir, 'request.normalized.json'), normalizedRequest),
|
|
1102
|
+
source_model_normalized: writeJsonArtifact(path.join(outDir, 'source-model.normalized.json'), sourceModel.sourceModelJson),
|
|
1103
|
+
source_model_summary: writeJsonArtifact(path.join(outDir, 'source-model.summary.json'), projection.sourceModelSummary),
|
|
1104
|
+
projection_report: writeJsonArtifact(path.join(outDir, 'projection-report.json'), projection.report),
|
|
1105
|
+
process_projection_bundle: writeJsonArtifact(path.join(outDir, 'process-projection-bundle.json'), projection.bundle),
|
|
1106
|
+
};
|
|
1107
|
+
return {
|
|
1108
|
+
generated_at_utc: nowIso(options.now),
|
|
1109
|
+
request_path: requestPath,
|
|
1110
|
+
out_dir: outDir,
|
|
1111
|
+
status: String(projection.report.status),
|
|
1112
|
+
projected_process_count: 1,
|
|
1113
|
+
relation_count: 1,
|
|
1114
|
+
source_model: {
|
|
1115
|
+
id: projection.sourceModelSummary.id,
|
|
1116
|
+
version: projection.sourceModelSummary.version,
|
|
1117
|
+
name: projection.sourceModelSummary.name,
|
|
1118
|
+
json_ordered_path: projection.sourceModelSummary.json_ordered_path,
|
|
1119
|
+
reference_to_resulting_process_id: projection.sourceModelSummary.reference_to_resulting_process_id,
|
|
1120
|
+
reference_to_resulting_process_version: projection.sourceModelSummary.reference_to_resulting_process_version,
|
|
1121
|
+
reference_process_instance_id: projection.sourceModelSummary.reference_process_instance_id,
|
|
1122
|
+
},
|
|
1123
|
+
files,
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
// Exposed for deterministic unit coverage of internal lifecyclemodel fallback logic.
|
|
1127
|
+
export const __testInternals = {
|
|
1128
|
+
uniqueStrings,
|
|
1129
|
+
toFiniteNumber,
|
|
1130
|
+
normalizeNumericOutput,
|
|
1131
|
+
toBoolean,
|
|
1132
|
+
resolveNameField,
|
|
1133
|
+
normalizedNameInfo,
|
|
1134
|
+
modelIdentifier,
|
|
1135
|
+
extractProcessInstances,
|
|
1136
|
+
extractEdges,
|
|
1137
|
+
processReferencePairs,
|
|
1138
|
+
parseProcessRecord,
|
|
1139
|
+
autoDetectProcessCatalogPath,
|
|
1140
|
+
autoDetectProcessJsonDirs,
|
|
1141
|
+
processSourceDirs,
|
|
1142
|
+
locateLocalProcessFile,
|
|
1143
|
+
loadSourceModel,
|
|
1144
|
+
referenceToResultingProcess,
|
|
1145
|
+
referenceProcessInstanceId,
|
|
1146
|
+
chooseReferenceInstance,
|
|
1147
|
+
cloneExchangeWithAmount,
|
|
1148
|
+
buildResultingProcessPayload,
|
|
1149
|
+
buildProjectionBundle,
|
|
1150
|
+
defaultOutDir,
|
|
1151
|
+
};
|
|
1152
|
+
//# sourceMappingURL=lifecyclemodel-resulting-process.js.map
|