@tiangong-lca/cli 0.0.2 → 0.0.4

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.
@@ -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"]}
@@ -1,9 +1,11 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';
4
+ import { createDatasetCommandClient } from './dataset-command.js';
5
+ import { readRuntimeEnv } from './env.js';
4
6
  import { CliError } from './errors.js';
5
7
  import { coerceText, deepGet, isRecord, loadRowsFromFile, } from './flow-governance.js';
6
- import { createSupabaseDataClient, requireSupabaseRestRuntime, runSupabaseArrayQuery, runSupabaseMutation, } from './supabase-client.js';
8
+ import { createSupabaseDataClient, requireSupabaseRestRuntime, runSupabaseArrayQuery, } from './supabase-client.js';
7
9
  import { createSupabaseDataRuntime } from './supabase-session.js';
8
10
  const DEFAULT_TIMEOUT_MS = 10_000;
9
11
  const DEFAULT_MAX_WORKERS = 4;
@@ -266,21 +268,19 @@ function build_error_reasons(stage, error) {
266
268
  ];
267
269
  }
268
270
  async function insert_flow_version(options) {
269
- const url = `${options.restBaseUrl.replace(/\/+$/u, '')}/flows`;
270
- await runSupabaseMutation(options.client.from('flows').insert({
271
+ await options.commandClient.create({
272
+ table: 'flows',
271
273
  id: options.rowId,
272
- json_ordered: options.payload,
273
- }), url);
274
+ jsonOrdered: options.payload,
275
+ });
274
276
  }
275
277
  async function update_flow_version(options) {
276
- const url = build_update_url(options.restBaseUrl, options.rowId, options.version);
277
- await runSupabaseMutation(options.client
278
- .from('flows')
279
- .update({
280
- json_ordered: options.payload,
281
- })
282
- .eq('id', options.rowId)
283
- .eq('version', options.version), url);
278
+ await options.commandClient.saveDraft({
279
+ table: 'flows',
280
+ id: options.rowId,
281
+ version: options.version,
282
+ jsonOrdered: options.payload,
283
+ });
284
284
  }
285
285
  async function sync_one_row(options) {
286
286
  try {
@@ -329,8 +329,7 @@ async function sync_one_row(options) {
329
329
  }
330
330
  if (ownBefore) {
331
331
  await update_flow_version({
332
- client: options.client,
333
- restBaseUrl: options.restBaseUrl,
332
+ commandClient: options.commandClient,
334
333
  rowId,
335
334
  version,
336
335
  payload,
@@ -352,8 +351,7 @@ async function sync_one_row(options) {
352
351
  }
353
352
  try {
354
353
  await insert_flow_version({
355
- client: options.client,
356
- restBaseUrl: options.restBaseUrl,
354
+ commandClient: options.commandClient,
357
355
  rowId,
358
356
  payload,
359
357
  });
@@ -377,8 +375,7 @@ async function sync_one_row(options) {
377
375
  if (ownAfter) {
378
376
  try {
379
377
  await update_flow_version({
380
- client: options.client,
381
- restBaseUrl: options.restBaseUrl,
378
+ commandClient: options.commandClient,
382
379
  rowId,
383
380
  version,
384
381
  payload,
@@ -459,6 +456,12 @@ export async function runFlowPublishVersion(options) {
459
456
  now,
460
457
  });
461
458
  const { client, restBaseUrl } = createSupabaseDataClient(runtime, fetchImpl, timeoutMs);
459
+ const commandClient = createDatasetCommandClient({
460
+ runtime,
461
+ fetchImpl,
462
+ timeoutMs,
463
+ region: readRuntimeEnv(options.env ?? process.env).region,
464
+ });
462
465
  const targetUserIdOverride = normalize_token(options.targetUserId ?? null);
463
466
  const files = build_output_files(outDir);
464
467
  let rows = loadRowsFromFile(inputFile);
@@ -475,6 +478,7 @@ export async function runFlowPublishVersion(options) {
475
478
  row,
476
479
  mode,
477
480
  client,
481
+ commandClient,
478
482
  restBaseUrl,
479
483
  targetUserIdOverride,
480
484
  }));