@tiangong-lca/cli 0.0.2 → 0.0.3
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 +131 -309
- package/dist/src/cli.js +158 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/flow-fetch-rows.js +235 -0
- package/dist/src/lib/flow-fetch-rows.js.map +1 -0
- package/dist/src/lib/flow-materialize-decisions.js +421 -0
- package/dist/src/lib/flow-materialize-decisions.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeJsonArtifact } from './artifacts.js';
|
|
4
|
+
import { CliError } from './errors.js';
|
|
5
|
+
import { extractFlowRecord, loadRowsFromFile, } from './flow-governance.js';
|
|
6
|
+
function normalizeToken(value) {
|
|
7
|
+
if (value === undefined || value === null) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const trimmed = String(value).trim();
|
|
11
|
+
return trimmed ? trimmed : null;
|
|
12
|
+
}
|
|
13
|
+
function assertInputFile(inputFile, requiredCode, missingCode) {
|
|
14
|
+
const normalized = normalizeToken(inputFile);
|
|
15
|
+
if (!normalized) {
|
|
16
|
+
throw new CliError('Missing required input file value.', {
|
|
17
|
+
code: requiredCode,
|
|
18
|
+
exitCode: 2,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const resolved = path.resolve(normalized);
|
|
22
|
+
if (!existsSync(resolved)) {
|
|
23
|
+
throw new CliError(`Input file not found: ${resolved}`, {
|
|
24
|
+
code: missingCode,
|
|
25
|
+
exitCode: 2,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return resolved;
|
|
29
|
+
}
|
|
30
|
+
function assertOutDir(outDir) {
|
|
31
|
+
const normalized = normalizeToken(outDir);
|
|
32
|
+
if (!normalized) {
|
|
33
|
+
throw new CliError('Missing required --out-dir value.', {
|
|
34
|
+
code: 'FLOW_MATERIALIZE_DECISIONS_OUT_DIR_REQUIRED',
|
|
35
|
+
exitCode: 2,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return path.resolve(normalized);
|
|
39
|
+
}
|
|
40
|
+
function parseFlowRefString(value, code) {
|
|
41
|
+
const trimmed = value.trim();
|
|
42
|
+
const [id, version, rest] = trimmed.split('@');
|
|
43
|
+
if (!id || !version || rest !== undefined) {
|
|
44
|
+
throw new CliError(`Expected flow ref to use id@version format, received: ${value}`, {
|
|
45
|
+
code,
|
|
46
|
+
exitCode: 2,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
id,
|
|
51
|
+
version,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function parseFlowRefLike(value, code) {
|
|
55
|
+
if (typeof value === 'string') {
|
|
56
|
+
return parseFlowRefString(value, code);
|
|
57
|
+
}
|
|
58
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
59
|
+
const record = value;
|
|
60
|
+
const id = normalizeToken(record.id);
|
|
61
|
+
const version = normalizeToken(record.version);
|
|
62
|
+
if (!id || !version) {
|
|
63
|
+
throw new CliError('Expected flow ref objects to contain id and version.', {
|
|
64
|
+
code,
|
|
65
|
+
exitCode: 2,
|
|
66
|
+
details: value,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
id,
|
|
71
|
+
version,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (value === undefined || value === null) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
throw new CliError('Expected flow ref values to be id@version strings or { id, version } objects.', {
|
|
78
|
+
code,
|
|
79
|
+
exitCode: 2,
|
|
80
|
+
details: value,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function parseFlowRefArray(row, keys, code) {
|
|
84
|
+
const refs = [];
|
|
85
|
+
keys.forEach((key) => {
|
|
86
|
+
const value = row[key];
|
|
87
|
+
if (!Array.isArray(value)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
value.forEach((item) => {
|
|
91
|
+
const ref = parseFlowRefLike(item, code);
|
|
92
|
+
if (ref) {
|
|
93
|
+
refs.push(ref);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
return refs;
|
|
98
|
+
}
|
|
99
|
+
function uniqueFlowRefs(refs) {
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
const unique = [];
|
|
102
|
+
refs.forEach((ref) => {
|
|
103
|
+
const key = `${ref.id}@${ref.version}`;
|
|
104
|
+
if (seen.has(key)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
seen.add(key);
|
|
108
|
+
unique.push(ref);
|
|
109
|
+
});
|
|
110
|
+
return unique;
|
|
111
|
+
}
|
|
112
|
+
function parseDecisionRow(row, index) {
|
|
113
|
+
const clusterId = normalizeToken(row.cluster_id ?? row.clusterId);
|
|
114
|
+
if (!clusterId) {
|
|
115
|
+
throw new CliError(`Decision row ${index + 1} is missing cluster_id.`, {
|
|
116
|
+
code: 'FLOW_MATERIALIZE_DECISIONS_CLUSTER_ID_REQUIRED',
|
|
117
|
+
exitCode: 2,
|
|
118
|
+
details: row,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const rawDecision = normalizeToken(row.decision ?? row.approved_decision ?? row.outcome);
|
|
122
|
+
if (rawDecision !== 'merge_keep_one' &&
|
|
123
|
+
rawDecision !== 'keep_distinct' &&
|
|
124
|
+
rawDecision !== 'blocked_missing_db_flow') {
|
|
125
|
+
throw new CliError(`Decision row ${clusterId} must use one of merge_keep_one, keep_distinct, or blocked_missing_db_flow.`, {
|
|
126
|
+
code: 'FLOW_MATERIALIZE_DECISIONS_INVALID_DECISION',
|
|
127
|
+
exitCode: 2,
|
|
128
|
+
details: row,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const canonicalRef = parseFlowRefLike(row.canonical_flow ??
|
|
132
|
+
row.canonical_ref ??
|
|
133
|
+
row.keep ??
|
|
134
|
+
row.keep_ref ??
|
|
135
|
+
row.keep_flow ??
|
|
136
|
+
row.canonical_flow_key, 'FLOW_MATERIALIZE_DECISIONS_INVALID_CANONICAL_REF');
|
|
137
|
+
const memberRefs = uniqueFlowRefs([
|
|
138
|
+
...parseFlowRefArray(row, [
|
|
139
|
+
'flow_refs',
|
|
140
|
+
'cluster_members',
|
|
141
|
+
'members',
|
|
142
|
+
'flows',
|
|
143
|
+
'flow_keys',
|
|
144
|
+
'member_keys',
|
|
145
|
+
'drop_refs',
|
|
146
|
+
'drops',
|
|
147
|
+
'rewrite_sources',
|
|
148
|
+
], 'FLOW_MATERIALIZE_DECISIONS_INVALID_MEMBER_REF'),
|
|
149
|
+
...(canonicalRef ? [canonicalRef] : []),
|
|
150
|
+
]);
|
|
151
|
+
if (rawDecision === 'merge_keep_one' && !canonicalRef) {
|
|
152
|
+
throw new CliError(`Decision row ${clusterId} is missing canonical flow information.`, {
|
|
153
|
+
code: 'FLOW_MATERIALIZE_DECISIONS_CANONICAL_REQUIRED',
|
|
154
|
+
exitCode: 2,
|
|
155
|
+
details: row,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (rawDecision === 'merge_keep_one' && memberRefs.length < 2) {
|
|
159
|
+
throw new CliError(`Decision row ${clusterId} needs at least two cluster members for merge_keep_one.`, {
|
|
160
|
+
code: 'FLOW_MATERIALIZE_DECISIONS_MEMBERS_REQUIRED',
|
|
161
|
+
exitCode: 2,
|
|
162
|
+
details: row,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
clusterId,
|
|
167
|
+
decision: rawDecision,
|
|
168
|
+
reason: normalizeToken(row.reason ?? row.notes ?? row.message),
|
|
169
|
+
canonicalRef,
|
|
170
|
+
memberRefs,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function buildFlowIndex(rows) {
|
|
174
|
+
const index = new Map();
|
|
175
|
+
rows.forEach((row, rowIndex) => {
|
|
176
|
+
const record = extractFlowRecord(row);
|
|
177
|
+
const key = `${record.id}@${record.version}`;
|
|
178
|
+
if (index.has(key)) {
|
|
179
|
+
throw new CliError(`Duplicate flow row detected for ${key}.`, {
|
|
180
|
+
code: 'FLOW_MATERIALIZE_DECISIONS_DUPLICATE_FLOW_ROW',
|
|
181
|
+
exitCode: 2,
|
|
182
|
+
details: {
|
|
183
|
+
flow_key: key,
|
|
184
|
+
first_index: [...index.keys()].indexOf(key),
|
|
185
|
+
duplicate_index: rowIndex,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
index.set(key, {
|
|
190
|
+
key,
|
|
191
|
+
row,
|
|
192
|
+
record,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
return index;
|
|
196
|
+
}
|
|
197
|
+
function summarizeFlow(record) {
|
|
198
|
+
return {
|
|
199
|
+
id: record.id,
|
|
200
|
+
version: record.version,
|
|
201
|
+
name: record.name,
|
|
202
|
+
flow_type: record.flowType,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function buildOutputFiles(outDir) {
|
|
206
|
+
return {
|
|
207
|
+
canonical_map: path.join(outDir, 'flow-dedup-canonical-map.json'),
|
|
208
|
+
rewrite_plan: path.join(outDir, 'flow-dedup-rewrite-plan.json'),
|
|
209
|
+
semantic_merge_seed: path.join(outDir, 'manual-semantic-merge-seed.current.json'),
|
|
210
|
+
summary: path.join(outDir, 'decision-summary.json'),
|
|
211
|
+
blocked_clusters: path.join(outDir, 'blocked-clusters.json'),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function incrementCount(record, key) {
|
|
215
|
+
record[key] = (record[key] ?? 0) + 1;
|
|
216
|
+
}
|
|
217
|
+
function sortedObject(entries) {
|
|
218
|
+
return Object.fromEntries(entries.sort(([left], [right]) => left.localeCompare(right)));
|
|
219
|
+
}
|
|
220
|
+
export async function runFlowMaterializeDecisions(options) {
|
|
221
|
+
const decisionFile = assertInputFile(options.decisionFile, 'FLOW_MATERIALIZE_DECISIONS_DECISION_FILE_REQUIRED', 'FLOW_MATERIALIZE_DECISIONS_DECISION_FILE_NOT_FOUND');
|
|
222
|
+
const flowRowsFile = assertInputFile(options.flowRowsFile, 'FLOW_MATERIALIZE_DECISIONS_FLOW_ROWS_FILE_REQUIRED', 'FLOW_MATERIALIZE_DECISIONS_FLOW_ROWS_FILE_NOT_FOUND');
|
|
223
|
+
const outDir = assertOutDir(options.outDir);
|
|
224
|
+
const generatedAt = (options.now ?? new Date()).toISOString();
|
|
225
|
+
const files = buildOutputFiles(outDir);
|
|
226
|
+
const decisions = loadRowsFromFile(decisionFile).map((row, index) => parseDecisionRow(row, index));
|
|
227
|
+
const flowIndex = buildFlowIndex(loadRowsFromFile(flowRowsFile));
|
|
228
|
+
const canonicalClusters = [];
|
|
229
|
+
const canonicalMapEntries = [];
|
|
230
|
+
const rewriteActions = [];
|
|
231
|
+
const seedAliasEntries = [];
|
|
232
|
+
const blockedClusters = [];
|
|
233
|
+
const decisionCounts = {
|
|
234
|
+
merge_keep_one: 0,
|
|
235
|
+
keep_distinct: 0,
|
|
236
|
+
blocked_missing_db_flow: 0,
|
|
237
|
+
};
|
|
238
|
+
const blockedReasonCounts = {};
|
|
239
|
+
decisions.forEach((decision) => {
|
|
240
|
+
decisionCounts[decision.decision] += 1;
|
|
241
|
+
if (decision.decision === 'keep_distinct') {
|
|
242
|
+
blockedClusters.push({
|
|
243
|
+
cluster_id: decision.clusterId,
|
|
244
|
+
decision: decision.decision,
|
|
245
|
+
blocker_code: 'decision_keep_distinct',
|
|
246
|
+
reason: decision.reason,
|
|
247
|
+
canonical_flow: decision.canonicalRef,
|
|
248
|
+
cluster_members: decision.memberRefs,
|
|
249
|
+
missing_flow_keys: [],
|
|
250
|
+
});
|
|
251
|
+
incrementCount(blockedReasonCounts, 'decision_keep_distinct');
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (decision.decision === 'blocked_missing_db_flow') {
|
|
255
|
+
blockedClusters.push({
|
|
256
|
+
cluster_id: decision.clusterId,
|
|
257
|
+
decision: decision.decision,
|
|
258
|
+
blocker_code: 'blocked_missing_db_flow',
|
|
259
|
+
reason: decision.reason,
|
|
260
|
+
canonical_flow: decision.canonicalRef,
|
|
261
|
+
cluster_members: decision.memberRefs,
|
|
262
|
+
missing_flow_keys: [],
|
|
263
|
+
});
|
|
264
|
+
incrementCount(blockedReasonCounts, 'blocked_missing_db_flow');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const canonicalRef = decision.canonicalRef;
|
|
268
|
+
const canonicalKey = `${canonicalRef.id}@${canonicalRef.version}`;
|
|
269
|
+
const missingFlowKeys = decision.memberRefs
|
|
270
|
+
.map((ref) => `${ref.id}@${ref.version}`)
|
|
271
|
+
.filter((key) => !flowIndex.has(key));
|
|
272
|
+
if (!flowIndex.has(canonicalKey)) {
|
|
273
|
+
blockedClusters.push({
|
|
274
|
+
cluster_id: decision.clusterId,
|
|
275
|
+
decision: decision.decision,
|
|
276
|
+
blocker_code: 'merge_canonical_flow_missing',
|
|
277
|
+
reason: decision.reason,
|
|
278
|
+
canonical_flow: canonicalRef,
|
|
279
|
+
cluster_members: decision.memberRefs,
|
|
280
|
+
missing_flow_keys: [...new Set([canonicalKey, ...missingFlowKeys])],
|
|
281
|
+
});
|
|
282
|
+
incrementCount(blockedReasonCounts, 'merge_canonical_flow_missing');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (missingFlowKeys.length > 0) {
|
|
286
|
+
blockedClusters.push({
|
|
287
|
+
cluster_id: decision.clusterId,
|
|
288
|
+
decision: decision.decision,
|
|
289
|
+
blocker_code: 'flow_row_missing',
|
|
290
|
+
reason: decision.reason,
|
|
291
|
+
canonical_flow: canonicalRef,
|
|
292
|
+
cluster_members: decision.memberRefs,
|
|
293
|
+
missing_flow_keys: [...new Set(missingFlowKeys)].sort(),
|
|
294
|
+
});
|
|
295
|
+
incrementCount(blockedReasonCounts, 'flow_row_missing');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const canonicalFlow = flowIndex.get(canonicalKey);
|
|
299
|
+
const mergeReason = decision.reason ?? 'approved_merge_keep_one';
|
|
300
|
+
const mergedFlows = decision.memberRefs
|
|
301
|
+
.map((ref) => flowIndex.get(`${ref.id}@${ref.version}`))
|
|
302
|
+
.sort((left, right) => left.key.localeCompare(right.key))
|
|
303
|
+
.map((entry) => ({
|
|
304
|
+
...summarizeFlow(entry.record),
|
|
305
|
+
relation: entry.key === canonicalKey
|
|
306
|
+
? 'canonical_self'
|
|
307
|
+
: 'rewrite_to_canonical',
|
|
308
|
+
}));
|
|
309
|
+
canonicalClusters.push({
|
|
310
|
+
cluster_id: decision.clusterId,
|
|
311
|
+
decision: 'merge_keep_one',
|
|
312
|
+
reason: decision.reason,
|
|
313
|
+
canonical_flow: summarizeFlow(canonicalFlow.record),
|
|
314
|
+
merged_flows: mergedFlows,
|
|
315
|
+
});
|
|
316
|
+
mergedFlows.forEach((flow) => {
|
|
317
|
+
canonicalMapEntries.push([
|
|
318
|
+
`${flow.id}@${flow.version}`,
|
|
319
|
+
{
|
|
320
|
+
id: canonicalFlow.record.id,
|
|
321
|
+
version: canonicalFlow.record.version,
|
|
322
|
+
cluster_id: decision.clusterId,
|
|
323
|
+
relation: flow.relation,
|
|
324
|
+
reason: mergeReason,
|
|
325
|
+
},
|
|
326
|
+
]);
|
|
327
|
+
if (flow.relation === 'rewrite_to_canonical') {
|
|
328
|
+
rewriteActions.push({
|
|
329
|
+
cluster_id: decision.clusterId,
|
|
330
|
+
action: 'rewrite_to_canonical',
|
|
331
|
+
reason: mergeReason,
|
|
332
|
+
source_flow_id: flow.id,
|
|
333
|
+
source_flow_version: flow.version,
|
|
334
|
+
source_flow_name: flow.name,
|
|
335
|
+
source_flow_type: flow.flow_type,
|
|
336
|
+
target_flow_id: canonicalFlow.record.id,
|
|
337
|
+
target_flow_version: canonicalFlow.record.version,
|
|
338
|
+
target_flow_name: canonicalFlow.record.name,
|
|
339
|
+
target_flow_type: canonicalFlow.record.flowType,
|
|
340
|
+
});
|
|
341
|
+
seedAliasEntries.push([
|
|
342
|
+
`${flow.id}@${flow.version}`,
|
|
343
|
+
{
|
|
344
|
+
id: canonicalFlow.record.id,
|
|
345
|
+
version: canonicalFlow.record.version,
|
|
346
|
+
cluster_id: decision.clusterId,
|
|
347
|
+
relation: 'rewrite_to_canonical',
|
|
348
|
+
reason: mergeReason,
|
|
349
|
+
},
|
|
350
|
+
]);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
const canonicalMapByFlowKey = sortedObject(canonicalMapEntries);
|
|
355
|
+
const semanticMergeSeed = sortedObject(seedAliasEntries.map(([key, value]) => [
|
|
356
|
+
key,
|
|
357
|
+
{
|
|
358
|
+
id: value.id,
|
|
359
|
+
version: value.version,
|
|
360
|
+
reason: value.reason,
|
|
361
|
+
cluster_id: value.cluster_id,
|
|
362
|
+
},
|
|
363
|
+
]));
|
|
364
|
+
const report = {
|
|
365
|
+
schema_version: 1,
|
|
366
|
+
generated_at_utc: generatedAt,
|
|
367
|
+
status: blockedClusters.length > 0
|
|
368
|
+
? 'completed_local_flow_decision_materialization_with_blocked_clusters'
|
|
369
|
+
: 'completed_local_flow_decision_materialization',
|
|
370
|
+
decision_file: decisionFile,
|
|
371
|
+
flow_rows_file: flowRowsFile,
|
|
372
|
+
out_dir: outDir,
|
|
373
|
+
counts: {
|
|
374
|
+
input_decisions: decisions.length,
|
|
375
|
+
materialized_clusters: canonicalClusters.length,
|
|
376
|
+
blocked_clusters: blockedClusters.length,
|
|
377
|
+
canonical_map_entries: Object.keys(canonicalMapByFlowKey).length,
|
|
378
|
+
rewrite_actions: rewriteActions.length,
|
|
379
|
+
seed_alias_entries: Object.keys(semanticMergeSeed).length,
|
|
380
|
+
decision_counts: decisionCounts,
|
|
381
|
+
blocked_reason_counts: blockedReasonCounts,
|
|
382
|
+
},
|
|
383
|
+
files,
|
|
384
|
+
};
|
|
385
|
+
writeJsonArtifact(files.canonical_map, {
|
|
386
|
+
schema_version: 1,
|
|
387
|
+
generated_at_utc: generatedAt,
|
|
388
|
+
clusters: canonicalClusters.sort((left, right) => left.cluster_id.localeCompare(right.cluster_id)),
|
|
389
|
+
by_flow_key: canonicalMapByFlowKey,
|
|
390
|
+
});
|
|
391
|
+
writeJsonArtifact(files.rewrite_plan, {
|
|
392
|
+
schema_version: 1,
|
|
393
|
+
generated_at_utc: generatedAt,
|
|
394
|
+
actions: rewriteActions.sort((left, right) => {
|
|
395
|
+
const clusterOrder = left.cluster_id.localeCompare(right.cluster_id);
|
|
396
|
+
if (clusterOrder !== 0) {
|
|
397
|
+
return clusterOrder;
|
|
398
|
+
}
|
|
399
|
+
const leftKey = `${left.source_flow_id}@${left.source_flow_version}`;
|
|
400
|
+
const rightKey = `${right.source_flow_id}@${right.source_flow_version}`;
|
|
401
|
+
return leftKey.localeCompare(rightKey);
|
|
402
|
+
}),
|
|
403
|
+
});
|
|
404
|
+
writeJsonArtifact(files.semantic_merge_seed, semanticMergeSeed);
|
|
405
|
+
writeJsonArtifact(files.blocked_clusters, {
|
|
406
|
+
schema_version: 1,
|
|
407
|
+
generated_at_utc: generatedAt,
|
|
408
|
+
clusters: blockedClusters.sort((left, right) => left.cluster_id.localeCompare(right.cluster_id)),
|
|
409
|
+
});
|
|
410
|
+
writeJsonArtifact(files.summary, report);
|
|
411
|
+
return report;
|
|
412
|
+
}
|
|
413
|
+
export const __testInternals = {
|
|
414
|
+
parseDecisionRow,
|
|
415
|
+
parseFlowRefLike,
|
|
416
|
+
parseFlowRefString,
|
|
417
|
+
uniqueFlowRefs,
|
|
418
|
+
buildFlowIndex,
|
|
419
|
+
buildOutputFiles,
|
|
420
|
+
};
|
|
421
|
+
//# sourceMappingURL=flow-materialize-decisions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-materialize-decisions.js","sourceRoot":"","sources":["../../../src/lib/flow-materialize-decisions.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { writeJsonArtifact } from './artifacts.js';\nimport { CliError } from './errors.js';\nimport {\n extractFlowRecord,\n loadRowsFromFile,\n type FlowRecord,\n type JsonRecord,\n} from './flow-governance.js';\n\ntype FlowRef = {\n id: string;\n version: string;\n};\n\ntype SupportedDecision = 'merge_keep_one' | 'keep_distinct' | 'blocked_missing_db_flow';\n\ntype NormalizedDecision = {\n clusterId: string;\n decision: SupportedDecision;\n reason: string | null;\n canonicalRef: FlowRef | null;\n memberRefs: FlowRef[];\n};\n\ntype IndexedFlowRow = {\n key: string;\n row: JsonRecord;\n record: FlowRecord;\n};\n\ntype CanonicalFlowSummary = {\n id: string;\n version: string;\n name: string;\n flow_type: string;\n};\n\ntype CanonicalMapCluster = {\n cluster_id: string;\n decision: 'merge_keep_one';\n reason: string | null;\n canonical_flow: CanonicalFlowSummary;\n merged_flows: Array<\n CanonicalFlowSummary & {\n relation: 'canonical_self' | 'rewrite_to_canonical';\n }\n >;\n};\n\ntype CanonicalMapEntry = {\n id: string;\n version: string;\n cluster_id: string;\n relation: 'canonical_self' | 'rewrite_to_canonical';\n reason: string;\n};\n\ntype RewritePlanAction = {\n cluster_id: string;\n action: 'rewrite_to_canonical';\n reason: string;\n source_flow_id: string;\n source_flow_version: string;\n source_flow_name: string;\n source_flow_type: string;\n target_flow_id: string;\n target_flow_version: string;\n target_flow_name: string;\n target_flow_type: string;\n};\n\ntype BlockedCluster = {\n cluster_id: string;\n decision: SupportedDecision;\n blocker_code:\n | 'decision_keep_distinct'\n | 'blocked_missing_db_flow'\n | 'merge_canonical_flow_missing'\n | 'flow_row_missing';\n reason: string | null;\n canonical_flow: FlowRef | null;\n cluster_members: FlowRef[];\n missing_flow_keys: string[];\n};\n\ntype FlowMaterializeDecisionsFiles = {\n canonical_map: string;\n rewrite_plan: string;\n semantic_merge_seed: string;\n summary: string;\n blocked_clusters: string;\n};\n\ntype FlowMaterializeDecisionCounts = {\n input_decisions: number;\n materialized_clusters: number;\n blocked_clusters: number;\n canonical_map_entries: number;\n rewrite_actions: number;\n seed_alias_entries: number;\n decision_counts: Record<SupportedDecision, number>;\n blocked_reason_counts: Record<string, number>;\n};\n\nexport type FlowMaterializeDecisionsReport = {\n schema_version: 1;\n generated_at_utc: string;\n status:\n | 'completed_local_flow_decision_materialization'\n | 'completed_local_flow_decision_materialization_with_blocked_clusters';\n decision_file: string;\n flow_rows_file: string;\n out_dir: string;\n counts: FlowMaterializeDecisionCounts;\n files: FlowMaterializeDecisionsFiles;\n};\n\nexport type RunFlowMaterializeDecisionsOptions = {\n decisionFile: string;\n flowRowsFile: string;\n outDir: string;\n now?: Date;\n};\n\nfunction normalizeToken(value: unknown): string | null {\n if (value === undefined || value === null) {\n return null;\n }\n\n const trimmed = String(value).trim();\n return trimmed ? trimmed : null;\n}\n\nfunction assertInputFile(inputFile: string, requiredCode: string, missingCode: string): string {\n const normalized = normalizeToken(inputFile);\n if (!normalized) {\n throw new CliError('Missing required input file value.', {\n code: requiredCode,\n exitCode: 2,\n });\n }\n\n const resolved = path.resolve(normalized);\n if (!existsSync(resolved)) {\n throw new CliError(`Input file not found: ${resolved}`, {\n code: missingCode,\n exitCode: 2,\n });\n }\n\n return resolved;\n}\n\nfunction assertOutDir(outDir: string): string {\n const normalized = normalizeToken(outDir);\n if (!normalized) {\n throw new CliError('Missing required --out-dir value.', {\n code: 'FLOW_MATERIALIZE_DECISIONS_OUT_DIR_REQUIRED',\n exitCode: 2,\n });\n }\n\n return path.resolve(normalized);\n}\n\nfunction parseFlowRefString(value: string, code: string): FlowRef {\n const trimmed = value.trim();\n const [id, version, rest] = trimmed.split('@');\n if (!id || !version || rest !== undefined) {\n throw new CliError(`Expected flow ref to use id@version format, received: ${value}`, {\n code,\n exitCode: 2,\n });\n }\n\n return {\n id,\n version,\n };\n}\n\nfunction parseFlowRefLike(value: unknown, code: string): FlowRef | null {\n if (typeof value === 'string') {\n return parseFlowRefString(value, code);\n }\n\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const record = value as JsonRecord;\n const id = normalizeToken(record.id);\n const version = normalizeToken(record.version);\n if (!id || !version) {\n throw new CliError('Expected flow ref objects to contain id and version.', {\n code,\n exitCode: 2,\n details: value,\n });\n }\n return {\n id,\n version,\n };\n }\n\n if (value === undefined || value === null) {\n return null;\n }\n\n throw new CliError(\n 'Expected flow ref values to be id@version strings or { id, version } objects.',\n {\n code,\n exitCode: 2,\n details: value,\n },\n );\n}\n\nfunction parseFlowRefArray(row: JsonRecord, keys: string[], code: string): FlowRef[] {\n const refs: FlowRef[] = [];\n keys.forEach((key) => {\n const value = row[key];\n if (!Array.isArray(value)) {\n return;\n }\n value.forEach((item) => {\n const ref = parseFlowRefLike(item, code);\n if (ref) {\n refs.push(ref);\n }\n });\n });\n return refs;\n}\n\nfunction uniqueFlowRefs(refs: FlowRef[]): FlowRef[] {\n const seen = new Set<string>();\n const unique: FlowRef[] = [];\n refs.forEach((ref) => {\n const key = `${ref.id}@${ref.version}`;\n if (seen.has(key)) {\n return;\n }\n seen.add(key);\n unique.push(ref);\n });\n return unique;\n}\n\nfunction parseDecisionRow(row: JsonRecord, index: number): NormalizedDecision {\n const clusterId = normalizeToken(row.cluster_id ?? row.clusterId);\n if (!clusterId) {\n throw new CliError(`Decision row ${index + 1} is missing cluster_id.`, {\n code: 'FLOW_MATERIALIZE_DECISIONS_CLUSTER_ID_REQUIRED',\n exitCode: 2,\n details: row,\n });\n }\n\n const rawDecision = normalizeToken(row.decision ?? row.approved_decision ?? row.outcome);\n if (\n rawDecision !== 'merge_keep_one' &&\n rawDecision !== 'keep_distinct' &&\n rawDecision !== 'blocked_missing_db_flow'\n ) {\n throw new CliError(\n `Decision row ${clusterId} must use one of merge_keep_one, keep_distinct, or blocked_missing_db_flow.`,\n {\n code: 'FLOW_MATERIALIZE_DECISIONS_INVALID_DECISION',\n exitCode: 2,\n details: row,\n },\n );\n }\n\n const canonicalRef = parseFlowRefLike(\n row.canonical_flow ??\n row.canonical_ref ??\n row.keep ??\n row.keep_ref ??\n row.keep_flow ??\n row.canonical_flow_key,\n 'FLOW_MATERIALIZE_DECISIONS_INVALID_CANONICAL_REF',\n );\n const memberRefs = uniqueFlowRefs([\n ...parseFlowRefArray(\n row,\n [\n 'flow_refs',\n 'cluster_members',\n 'members',\n 'flows',\n 'flow_keys',\n 'member_keys',\n 'drop_refs',\n 'drops',\n 'rewrite_sources',\n ],\n 'FLOW_MATERIALIZE_DECISIONS_INVALID_MEMBER_REF',\n ),\n ...(canonicalRef ? [canonicalRef] : []),\n ]);\n\n if (rawDecision === 'merge_keep_one' && !canonicalRef) {\n throw new CliError(`Decision row ${clusterId} is missing canonical flow information.`, {\n code: 'FLOW_MATERIALIZE_DECISIONS_CANONICAL_REQUIRED',\n exitCode: 2,\n details: row,\n });\n }\n\n if (rawDecision === 'merge_keep_one' && memberRefs.length < 2) {\n throw new CliError(\n `Decision row ${clusterId} needs at least two cluster members for merge_keep_one.`,\n {\n code: 'FLOW_MATERIALIZE_DECISIONS_MEMBERS_REQUIRED',\n exitCode: 2,\n details: row,\n },\n );\n }\n\n return {\n clusterId,\n decision: rawDecision,\n reason: normalizeToken(row.reason ?? row.notes ?? row.message),\n canonicalRef,\n memberRefs,\n };\n}\n\nfunction buildFlowIndex(rows: JsonRecord[]): Map<string, IndexedFlowRow> {\n const index = new Map<string, IndexedFlowRow>();\n rows.forEach((row, rowIndex) => {\n const record = extractFlowRecord(row);\n const key = `${record.id}@${record.version}`;\n if (index.has(key)) {\n throw new CliError(`Duplicate flow row detected for ${key}.`, {\n code: 'FLOW_MATERIALIZE_DECISIONS_DUPLICATE_FLOW_ROW',\n exitCode: 2,\n details: {\n flow_key: key,\n first_index: [...index.keys()].indexOf(key),\n duplicate_index: rowIndex,\n },\n });\n }\n index.set(key, {\n key,\n row,\n record,\n });\n });\n return index;\n}\n\nfunction summarizeFlow(record: FlowRecord): CanonicalFlowSummary {\n return {\n id: record.id,\n version: record.version,\n name: record.name,\n flow_type: record.flowType,\n };\n}\n\nfunction buildOutputFiles(outDir: string): FlowMaterializeDecisionsFiles {\n return {\n canonical_map: path.join(outDir, 'flow-dedup-canonical-map.json'),\n rewrite_plan: path.join(outDir, 'flow-dedup-rewrite-plan.json'),\n semantic_merge_seed: path.join(outDir, 'manual-semantic-merge-seed.current.json'),\n summary: path.join(outDir, 'decision-summary.json'),\n blocked_clusters: path.join(outDir, 'blocked-clusters.json'),\n };\n}\n\nfunction incrementCount(record: Record<string, number>, key: string): void {\n record[key] = (record[key] ?? 0) + 1;\n}\n\nfunction sortedObject<T>(entries: Array<[string, T]>): Record<string, T> {\n return Object.fromEntries(entries.sort(([left], [right]) => left.localeCompare(right)));\n}\n\nexport async function runFlowMaterializeDecisions(\n options: RunFlowMaterializeDecisionsOptions,\n): Promise<FlowMaterializeDecisionsReport> {\n const decisionFile = assertInputFile(\n options.decisionFile,\n 'FLOW_MATERIALIZE_DECISIONS_DECISION_FILE_REQUIRED',\n 'FLOW_MATERIALIZE_DECISIONS_DECISION_FILE_NOT_FOUND',\n );\n const flowRowsFile = assertInputFile(\n options.flowRowsFile,\n 'FLOW_MATERIALIZE_DECISIONS_FLOW_ROWS_FILE_REQUIRED',\n 'FLOW_MATERIALIZE_DECISIONS_FLOW_ROWS_FILE_NOT_FOUND',\n );\n const outDir = assertOutDir(options.outDir);\n const generatedAt = (options.now ?? new Date()).toISOString();\n const files = buildOutputFiles(outDir);\n\n const decisions = loadRowsFromFile(decisionFile).map((row, index) =>\n parseDecisionRow(row, index),\n );\n const flowIndex = buildFlowIndex(loadRowsFromFile(flowRowsFile));\n\n const canonicalClusters: CanonicalMapCluster[] = [];\n const canonicalMapEntries: Array<[string, CanonicalMapEntry]> = [];\n const rewriteActions: RewritePlanAction[] = [];\n const seedAliasEntries: Array<[string, CanonicalMapEntry]> = [];\n const blockedClusters: BlockedCluster[] = [];\n const decisionCounts: FlowMaterializeDecisionCounts['decision_counts'] = {\n merge_keep_one: 0,\n keep_distinct: 0,\n blocked_missing_db_flow: 0,\n };\n const blockedReasonCounts: Record<string, number> = {};\n\n decisions.forEach((decision) => {\n decisionCounts[decision.decision] += 1;\n\n if (decision.decision === 'keep_distinct') {\n blockedClusters.push({\n cluster_id: decision.clusterId,\n decision: decision.decision,\n blocker_code: 'decision_keep_distinct',\n reason: decision.reason,\n canonical_flow: decision.canonicalRef,\n cluster_members: decision.memberRefs,\n missing_flow_keys: [],\n });\n incrementCount(blockedReasonCounts, 'decision_keep_distinct');\n return;\n }\n\n if (decision.decision === 'blocked_missing_db_flow') {\n blockedClusters.push({\n cluster_id: decision.clusterId,\n decision: decision.decision,\n blocker_code: 'blocked_missing_db_flow',\n reason: decision.reason,\n canonical_flow: decision.canonicalRef,\n cluster_members: decision.memberRefs,\n missing_flow_keys: [],\n });\n incrementCount(blockedReasonCounts, 'blocked_missing_db_flow');\n return;\n }\n\n const canonicalRef = decision.canonicalRef as FlowRef;\n const canonicalKey = `${canonicalRef.id}@${canonicalRef.version}`;\n const missingFlowKeys = decision.memberRefs\n .map((ref) => `${ref.id}@${ref.version}`)\n .filter((key) => !flowIndex.has(key));\n\n if (!flowIndex.has(canonicalKey)) {\n blockedClusters.push({\n cluster_id: decision.clusterId,\n decision: decision.decision,\n blocker_code: 'merge_canonical_flow_missing',\n reason: decision.reason,\n canonical_flow: canonicalRef,\n cluster_members: decision.memberRefs,\n missing_flow_keys: [...new Set([canonicalKey, ...missingFlowKeys])],\n });\n incrementCount(blockedReasonCounts, 'merge_canonical_flow_missing');\n return;\n }\n\n if (missingFlowKeys.length > 0) {\n blockedClusters.push({\n cluster_id: decision.clusterId,\n decision: decision.decision,\n blocker_code: 'flow_row_missing',\n reason: decision.reason,\n canonical_flow: canonicalRef,\n cluster_members: decision.memberRefs,\n missing_flow_keys: [...new Set(missingFlowKeys)].sort(),\n });\n incrementCount(blockedReasonCounts, 'flow_row_missing');\n return;\n }\n\n const canonicalFlow = flowIndex.get(canonicalKey) as IndexedFlowRow;\n const mergeReason = decision.reason ?? 'approved_merge_keep_one';\n const mergedFlows = decision.memberRefs\n .map((ref) => flowIndex.get(`${ref.id}@${ref.version}`) as IndexedFlowRow)\n .sort((left, right) => left.key.localeCompare(right.key))\n .map((entry) => ({\n ...summarizeFlow(entry.record),\n relation:\n entry.key === canonicalKey\n ? ('canonical_self' as const)\n : ('rewrite_to_canonical' as const),\n }));\n\n canonicalClusters.push({\n cluster_id: decision.clusterId,\n decision: 'merge_keep_one',\n reason: decision.reason,\n canonical_flow: summarizeFlow(canonicalFlow.record),\n merged_flows: mergedFlows,\n });\n\n mergedFlows.forEach((flow) => {\n canonicalMapEntries.push([\n `${flow.id}@${flow.version}`,\n {\n id: canonicalFlow.record.id,\n version: canonicalFlow.record.version,\n cluster_id: decision.clusterId,\n relation: flow.relation,\n reason: mergeReason,\n },\n ]);\n\n if (flow.relation === 'rewrite_to_canonical') {\n rewriteActions.push({\n cluster_id: decision.clusterId,\n action: 'rewrite_to_canonical',\n reason: mergeReason,\n source_flow_id: flow.id,\n source_flow_version: flow.version,\n source_flow_name: flow.name,\n source_flow_type: flow.flow_type,\n target_flow_id: canonicalFlow.record.id,\n target_flow_version: canonicalFlow.record.version,\n target_flow_name: canonicalFlow.record.name,\n target_flow_type: canonicalFlow.record.flowType,\n });\n seedAliasEntries.push([\n `${flow.id}@${flow.version}`,\n {\n id: canonicalFlow.record.id,\n version: canonicalFlow.record.version,\n cluster_id: decision.clusterId,\n relation: 'rewrite_to_canonical',\n reason: mergeReason,\n },\n ]);\n }\n });\n });\n\n const canonicalMapByFlowKey = sortedObject(canonicalMapEntries);\n const semanticMergeSeed = sortedObject(\n seedAliasEntries.map(([key, value]) => [\n key,\n {\n id: value.id,\n version: value.version,\n reason: value.reason,\n cluster_id: value.cluster_id,\n },\n ]),\n );\n const report: FlowMaterializeDecisionsReport = {\n schema_version: 1,\n generated_at_utc: generatedAt,\n status:\n blockedClusters.length > 0\n ? 'completed_local_flow_decision_materialization_with_blocked_clusters'\n : 'completed_local_flow_decision_materialization',\n decision_file: decisionFile,\n flow_rows_file: flowRowsFile,\n out_dir: outDir,\n counts: {\n input_decisions: decisions.length,\n materialized_clusters: canonicalClusters.length,\n blocked_clusters: blockedClusters.length,\n canonical_map_entries: Object.keys(canonicalMapByFlowKey).length,\n rewrite_actions: rewriteActions.length,\n seed_alias_entries: Object.keys(semanticMergeSeed).length,\n decision_counts: decisionCounts,\n blocked_reason_counts: blockedReasonCounts,\n },\n files,\n };\n\n writeJsonArtifact(files.canonical_map, {\n schema_version: 1,\n generated_at_utc: generatedAt,\n clusters: canonicalClusters.sort((left, right) =>\n left.cluster_id.localeCompare(right.cluster_id),\n ),\n by_flow_key: canonicalMapByFlowKey,\n });\n writeJsonArtifact(files.rewrite_plan, {\n schema_version: 1,\n generated_at_utc: generatedAt,\n actions: rewriteActions.sort((left, right) => {\n const clusterOrder = left.cluster_id.localeCompare(right.cluster_id);\n if (clusterOrder !== 0) {\n return clusterOrder;\n }\n const leftKey = `${left.source_flow_id}@${left.source_flow_version}`;\n const rightKey = `${right.source_flow_id}@${right.source_flow_version}`;\n return leftKey.localeCompare(rightKey);\n }),\n });\n writeJsonArtifact(files.semantic_merge_seed, semanticMergeSeed);\n writeJsonArtifact(files.blocked_clusters, {\n schema_version: 1,\n generated_at_utc: generatedAt,\n clusters: blockedClusters.sort((left, right) =>\n left.cluster_id.localeCompare(right.cluster_id),\n ),\n });\n writeJsonArtifact(files.summary, report);\n\n return report;\n}\n\nexport const __testInternals = {\n parseDecisionRow,\n parseFlowRefLike,\n parseFlowRefString,\n uniqueFlowRefs,\n buildFlowIndex,\n buildOutputFiles,\n};\n"]}
|