@tiangong-lca/cli 0.0.21 → 0.0.22
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 +53 -4
- package/dist/src/cli.js +331 -21
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-alias-rewrite.js +541 -0
- package/dist/src/lib/dataset-maintenance-alias-rewrite.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-apply.js +1537 -0
- package/dist/src/lib/dataset-maintenance-apply.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-contract.js +836 -0
- package/dist/src/lib/dataset-maintenance-contract.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-plan.js +485 -0
- package/dist/src/lib/dataset-maintenance-plan.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-remote.js +272 -0
- package/dist/src/lib/dataset-maintenance-remote.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-support-validation.js +18 -0
- package/dist/src/lib/dataset-maintenance-support-validation.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-verify.js +690 -0
- package/dist/src/lib/dataset-maintenance-verify.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { FlowPropertySchema, FlowSchema, ProcessSchema } from '@tiangong-lca/tidas-sdk';
|
|
2
|
+
import { isJsonObject, maintenanceRowKey, sha256Json, } from './dataset-maintenance-contract.js';
|
|
3
|
+
const DEFAULT_ALIAS_SCHEMAS = {
|
|
4
|
+
flowproperties: FlowPropertySchema,
|
|
5
|
+
flows: FlowSchema,
|
|
6
|
+
processes: ProcessSchema,
|
|
7
|
+
};
|
|
8
|
+
const ALIAS_PROFILES = {
|
|
9
|
+
time: {
|
|
10
|
+
factor: '0.00011415525114155251',
|
|
11
|
+
rows: 25,
|
|
12
|
+
flowproperties: 1,
|
|
13
|
+
flows: 10,
|
|
14
|
+
processes: 14,
|
|
15
|
+
exchanges: 20,
|
|
16
|
+
target_flow_refs: 106,
|
|
17
|
+
target_exchange_refs: 441,
|
|
18
|
+
},
|
|
19
|
+
length_time: {
|
|
20
|
+
factor: '1000',
|
|
21
|
+
rows: 27,
|
|
22
|
+
flowproperties: 1,
|
|
23
|
+
flows: 13,
|
|
24
|
+
processes: 13,
|
|
25
|
+
exchanges: 39,
|
|
26
|
+
target_flow_refs: 32,
|
|
27
|
+
target_exchange_refs: 3216,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const EXPECTED_UNRELATED_EXCHANGES = 309;
|
|
31
|
+
const DECIMAL_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/u;
|
|
32
|
+
function decimalParts(value) {
|
|
33
|
+
if (value.length > 256 || !DECIMAL_PATTERN.test(value)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const negative = value.startsWith('-');
|
|
37
|
+
const unsigned = negative ? value.slice(1) : value;
|
|
38
|
+
const [integer, fraction = ''] = unsigned.split('.');
|
|
39
|
+
return {
|
|
40
|
+
negative,
|
|
41
|
+
coefficient: BigInt(`${integer}${fraction}`),
|
|
42
|
+
scale: fraction.length,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function decimalEqual(left, right) {
|
|
46
|
+
const leftParts = decimalParts(left);
|
|
47
|
+
const rightParts = decimalParts(right);
|
|
48
|
+
if (!leftParts || !rightParts)
|
|
49
|
+
return false;
|
|
50
|
+
const scale = Math.max(leftParts.scale, rightParts.scale);
|
|
51
|
+
const leftCoefficient = leftParts.coefficient * 10n ** BigInt(scale - leftParts.scale);
|
|
52
|
+
const rightCoefficient = rightParts.coefficient * 10n ** BigInt(scale - rightParts.scale);
|
|
53
|
+
return (leftCoefficient === rightCoefficient &&
|
|
54
|
+
(leftCoefficient === 0n || leftParts.negative === rightParts.negative));
|
|
55
|
+
}
|
|
56
|
+
function decimalText(parts) {
|
|
57
|
+
let digits = parts.coefficient.toString().padStart(parts.scale + 1, '0');
|
|
58
|
+
if (parts.scale) {
|
|
59
|
+
const split = digits.length - parts.scale;
|
|
60
|
+
digits = `${digits.slice(0, split)}.${digits.slice(split)}`;
|
|
61
|
+
}
|
|
62
|
+
return `${parts.negative && parts.coefficient !== 0n ? '-' : ''}${digits}`;
|
|
63
|
+
}
|
|
64
|
+
export function multiplyExactDecimal(value, factor) {
|
|
65
|
+
const left = decimalParts(value);
|
|
66
|
+
const right = decimalParts(factor);
|
|
67
|
+
if (!left || !right) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return decimalText({
|
|
71
|
+
negative: left.negative !== right.negative,
|
|
72
|
+
coefficient: left.coefficient * right.coefficient,
|
|
73
|
+
scale: left.scale + right.scale,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function aliasBlocker(action, code, message, details) {
|
|
77
|
+
return {
|
|
78
|
+
code,
|
|
79
|
+
message,
|
|
80
|
+
action_id: action.action_id,
|
|
81
|
+
table: action.table,
|
|
82
|
+
id: action.id,
|
|
83
|
+
version: action.version,
|
|
84
|
+
...(details === undefined ? {} : { details }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function firstAction(actions, batchId) {
|
|
88
|
+
return actions.find((action) => action.batch_id === batchId) ?? null;
|
|
89
|
+
}
|
|
90
|
+
function addBatchBlocker(actions, batchId, code, message, details) {
|
|
91
|
+
const action = firstAction(actions, batchId);
|
|
92
|
+
if (!action) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
action.blockers.push(aliasBlocker(action, code, message, details));
|
|
96
|
+
action.status = 'blocked';
|
|
97
|
+
}
|
|
98
|
+
function clonePayload(payload) {
|
|
99
|
+
return structuredClone(payload);
|
|
100
|
+
}
|
|
101
|
+
function flowPropertyEntries(payload) {
|
|
102
|
+
const root = payload.flowDataSet;
|
|
103
|
+
const properties = isJsonObject(root) ? root.flowProperties : null;
|
|
104
|
+
const value = isJsonObject(properties) ? properties.flowProperty : null;
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
return value.every(isJsonObject) ? value : null;
|
|
107
|
+
}
|
|
108
|
+
return isJsonObject(value) ? [value] : null;
|
|
109
|
+
}
|
|
110
|
+
function flowPropertySingleton(payload) {
|
|
111
|
+
const root = payload.flowDataSet;
|
|
112
|
+
const properties = isJsonObject(root) ? root.flowProperties : null;
|
|
113
|
+
const value = isJsonObject(properties) ? properties.flowProperty : null;
|
|
114
|
+
return isJsonObject(value) ? value : null;
|
|
115
|
+
}
|
|
116
|
+
function processExchangeEntries(payload) {
|
|
117
|
+
const root = payload.processDataSet;
|
|
118
|
+
const exchanges = isJsonObject(root) ? root.exchanges : null;
|
|
119
|
+
const value = isJsonObject(exchanges) ? exchanges.exchange : null;
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
return value.every(isJsonObject) ? value : null;
|
|
122
|
+
}
|
|
123
|
+
return isJsonObject(value) ? [value] : null;
|
|
124
|
+
}
|
|
125
|
+
function referenceIdentity(value) {
|
|
126
|
+
return isJsonObject(value)
|
|
127
|
+
? {
|
|
128
|
+
id: typeof value['@refObjectId'] === 'string' ? value['@refObjectId'] : null,
|
|
129
|
+
version: typeof value['@version'] === 'string' ? value['@version'] : null,
|
|
130
|
+
}
|
|
131
|
+
: { id: null, version: null };
|
|
132
|
+
}
|
|
133
|
+
function referenceMatches(value, id, version) {
|
|
134
|
+
const identity = referenceIdentity(value);
|
|
135
|
+
return identity.id === id && (!version || identity.version === version);
|
|
136
|
+
}
|
|
137
|
+
function entityRefKey(id, version) {
|
|
138
|
+
return `${id}@${version}`;
|
|
139
|
+
}
|
|
140
|
+
function targetSnapshotValid(snapshot, table, batch, userId) {
|
|
141
|
+
const target = table === 'unitgroups' ? batch.target.unitgroup : batch.target.flowproperty;
|
|
142
|
+
return Boolean(snapshot &&
|
|
143
|
+
snapshot.table === table &&
|
|
144
|
+
snapshot.id === target.id &&
|
|
145
|
+
snapshot.version === target.version &&
|
|
146
|
+
snapshot.user_id === userId &&
|
|
147
|
+
snapshot.state_code === 0 &&
|
|
148
|
+
snapshot.modified_at &&
|
|
149
|
+
snapshot.json_ordered &&
|
|
150
|
+
snapshot.payload_sha256);
|
|
151
|
+
}
|
|
152
|
+
function sourceUnitGroupSnapshotValid(snapshot, batch, userId) {
|
|
153
|
+
return Boolean(snapshot &&
|
|
154
|
+
snapshot.table === 'unitgroups' &&
|
|
155
|
+
snapshot.id === batch.source.unitgroup.id &&
|
|
156
|
+
snapshot.version === batch.source.unitgroup.version &&
|
|
157
|
+
snapshot.user_id === userId &&
|
|
158
|
+
snapshot.state_code === 0 &&
|
|
159
|
+
snapshot.modified_at &&
|
|
160
|
+
snapshot.json_ordered &&
|
|
161
|
+
snapshot.payload_sha256);
|
|
162
|
+
}
|
|
163
|
+
function sourceReferenceUnit(snapshot, batch) {
|
|
164
|
+
const root = snapshot?.json_ordered?.unitGroupDataSet;
|
|
165
|
+
const information = isJsonObject(root) ? root.unitGroupInformation : null;
|
|
166
|
+
const quantitative = isJsonObject(information) ? information.quantitativeReference : null;
|
|
167
|
+
const referenceId = isJsonObject(quantitative) ? quantitative.referenceToReferenceUnit : null;
|
|
168
|
+
const units = isJsonObject(root) ? root.units : null;
|
|
169
|
+
const raw = isJsonObject(units) ? units.unit : null;
|
|
170
|
+
const entries = Array.isArray(raw) ? raw : [raw];
|
|
171
|
+
const reference = entries.find((entry) => isJsonObject(entry) && entry['@dataSetInternalID'] === referenceId);
|
|
172
|
+
const expectedName = batch.dimension === 'time' ? 'hr' : 'kmy';
|
|
173
|
+
return isJsonObject(reference) &&
|
|
174
|
+
reference.name === expectedName &&
|
|
175
|
+
typeof reference.meanValue === 'string' &&
|
|
176
|
+
decimalEqual(reference.meanValue, '1')
|
|
177
|
+
? clonePayload(reference)
|
|
178
|
+
: null;
|
|
179
|
+
}
|
|
180
|
+
function targetConversionUnit(options) {
|
|
181
|
+
const sourceName = options.source?.name;
|
|
182
|
+
const root = options.target?.json_ordered?.unitGroupDataSet;
|
|
183
|
+
const units = isJsonObject(root) ? root.units : null;
|
|
184
|
+
const raw = isJsonObject(units) ? units.unit : null;
|
|
185
|
+
const entries = Array.isArray(raw) ? raw : [raw];
|
|
186
|
+
const match = entries.find((entry) => isJsonObject(entry) &&
|
|
187
|
+
entry.name === sourceName &&
|
|
188
|
+
typeof entry.meanValue === 'string' &&
|
|
189
|
+
decimalEqual(entry.meanValue, options.factor));
|
|
190
|
+
return isJsonObject(match) ? clonePayload(match) : null;
|
|
191
|
+
}
|
|
192
|
+
function targetUnitGroupReferenceFromFlowProperty(snapshot, batch) {
|
|
193
|
+
const root = snapshot?.json_ordered?.flowPropertyDataSet;
|
|
194
|
+
const information = isJsonObject(root) ? root.flowPropertiesInformation : null;
|
|
195
|
+
const quantitative = isJsonObject(information) ? information.quantitativeReference : null;
|
|
196
|
+
const reference = isJsonObject(quantitative) ? quantitative.referenceToReferenceUnitGroup : null;
|
|
197
|
+
return isJsonObject(reference) &&
|
|
198
|
+
referenceMatches(reference, batch.target.unitgroup.id, batch.target.unitgroup.version)
|
|
199
|
+
? clonePayload(reference)
|
|
200
|
+
: null;
|
|
201
|
+
}
|
|
202
|
+
function canonicalFlowPropertyReference(rows, batch) {
|
|
203
|
+
const references = rows
|
|
204
|
+
.filter((row) => row.table === 'flows' && row.json_ordered)
|
|
205
|
+
.flatMap((row) => flowPropertyEntries(row.json_ordered) ?? [])
|
|
206
|
+
.map((entry) => entry.referenceToFlowPropertyDataSet)
|
|
207
|
+
.filter((reference) => isJsonObject(reference) &&
|
|
208
|
+
referenceMatches(reference, batch.target.flowproperty.id, batch.target.flowproperty.version));
|
|
209
|
+
const unique = new Map(references.map((reference) => [sha256Json(reference), reference]));
|
|
210
|
+
return unique.size === 1 ? clonePayload([...unique.values()][0]) : null;
|
|
211
|
+
}
|
|
212
|
+
function replaceAliasFlowProperty(payload, batch, targetReference) {
|
|
213
|
+
const desired = clonePayload(payload);
|
|
214
|
+
const root = desired.flowPropertyDataSet;
|
|
215
|
+
const information = isJsonObject(root) ? root.flowPropertiesInformation : null;
|
|
216
|
+
const quantitative = isJsonObject(information) ? information.quantitativeReference : null;
|
|
217
|
+
if (!isJsonObject(quantitative) ||
|
|
218
|
+
!referenceMatches(quantitative.referenceToReferenceUnitGroup, batch.source.unitgroup.id, batch.source.unitgroup.version)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
quantitative.referenceToReferenceUnitGroup = clonePayload(targetReference);
|
|
222
|
+
return desired;
|
|
223
|
+
}
|
|
224
|
+
function replaceFlowReferenceProperty(payload, batch, targetReference) {
|
|
225
|
+
const desired = clonePayload(payload);
|
|
226
|
+
const matching = flowPropertySingleton(desired);
|
|
227
|
+
if (!matching ||
|
|
228
|
+
!referenceMatches(matching.referenceToFlowPropertyDataSet, batch.source.flowproperty.id, batch.source.flowproperty.version) ||
|
|
229
|
+
matching['@dataSetInternalID'] !== '1' ||
|
|
230
|
+
typeof matching.meanValue !== 'string' ||
|
|
231
|
+
!decimalEqual(matching.meanValue, '1')) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
matching.referenceToFlowPropertyDataSet = clonePayload(targetReference);
|
|
235
|
+
return desired;
|
|
236
|
+
}
|
|
237
|
+
function rewriteProcessExchanges(options) {
|
|
238
|
+
const desired = clonePayload(options.payload);
|
|
239
|
+
const exchanges = processExchangeEntries(desired);
|
|
240
|
+
if (!exchanges || !options.action.exchange_instances) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const rewrites = [];
|
|
244
|
+
for (const instance of options.action.exchange_instances) {
|
|
245
|
+
const exchange = exchanges[instance.exchange_index];
|
|
246
|
+
if (!exchange ||
|
|
247
|
+
exchange['@dataSetInternalID'] !== instance.data_set_internal_id ||
|
|
248
|
+
!referenceMatches(exchange.referenceToFlowDataSet, instance.flow_id, instance.flow_version) ||
|
|
249
|
+
exchange.exchangeDirection !== instance.direction ||
|
|
250
|
+
sha256Json(exchange) !== instance.before_exchange_sha256 ||
|
|
251
|
+
exchange.meanAmount !== instance.before_mean_amount ||
|
|
252
|
+
exchange.resultingAmount !== instance.before_resulting_amount ||
|
|
253
|
+
typeof exchange.exchangeDirection !== 'string') {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const afterMean = multiplyExactDecimal(instance.before_mean_amount, options.batch.factor);
|
|
257
|
+
const afterResulting = multiplyExactDecimal(instance.before_resulting_amount, options.batch.factor);
|
|
258
|
+
if (afterMean === null || afterResulting === null) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
exchange.meanAmount = afterMean;
|
|
262
|
+
exchange.resultingAmount = afterResulting;
|
|
263
|
+
rewrites.push({
|
|
264
|
+
...instance,
|
|
265
|
+
action_id: options.action.action_id,
|
|
266
|
+
process_id: options.action.id,
|
|
267
|
+
process_version: options.action.version,
|
|
268
|
+
after_mean_amount: afterMean,
|
|
269
|
+
after_resulting_amount: afterResulting,
|
|
270
|
+
after_exchange_sha256: sha256Json(exchange),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return { payload: desired, rewrites };
|
|
274
|
+
}
|
|
275
|
+
function countFlowPropertyRefs(rows, flowpropertyId, flowpropertyVersion) {
|
|
276
|
+
return rows
|
|
277
|
+
.filter((row) => row.table === 'flows' && row.json_ordered)
|
|
278
|
+
.flatMap((row) => flowPropertyEntries(row.json_ordered) ?? [])
|
|
279
|
+
.filter((entry) => referenceMatches(entry.referenceToFlowPropertyDataSet, flowpropertyId, flowpropertyVersion)).length;
|
|
280
|
+
}
|
|
281
|
+
function countUnitGroupRefs(rows, unitgroupId, unitgroupVersion) {
|
|
282
|
+
return rows.filter((row) => {
|
|
283
|
+
if (row.table !== 'flowproperties' || !row.json_ordered)
|
|
284
|
+
return false;
|
|
285
|
+
const root = row.json_ordered.flowPropertyDataSet;
|
|
286
|
+
const information = isJsonObject(root) ? root.flowPropertiesInformation : null;
|
|
287
|
+
const quantitative = isJsonObject(information) ? information.quantitativeReference : null;
|
|
288
|
+
return (isJsonObject(quantitative) &&
|
|
289
|
+
referenceMatches(quantitative.referenceToReferenceUnitGroup, unitgroupId, unitgroupVersion));
|
|
290
|
+
}).length;
|
|
291
|
+
}
|
|
292
|
+
function flowsWithProperty(rows, flowpropertyId, flowpropertyVersion) {
|
|
293
|
+
return new Set(rows
|
|
294
|
+
.filter((row) => row.table === 'flows' &&
|
|
295
|
+
row.json_ordered &&
|
|
296
|
+
(flowPropertyEntries(row.json_ordered) ?? []).some((entry) => referenceMatches(entry.referenceToFlowPropertyDataSet, flowpropertyId, flowpropertyVersion)))
|
|
297
|
+
.map((row) => entityRefKey(row.id, row.version)));
|
|
298
|
+
}
|
|
299
|
+
function countExchangeFlowRefs(rows, flowRefs) {
|
|
300
|
+
return rows
|
|
301
|
+
.filter((row) => row.table === 'processes' && row.json_ordered)
|
|
302
|
+
.flatMap((row) => processExchangeEntries(row.json_ordered) ?? [])
|
|
303
|
+
.filter((exchange) => {
|
|
304
|
+
const identity = referenceIdentity(exchange.referenceToFlowDataSet);
|
|
305
|
+
return (identity.id !== null &&
|
|
306
|
+
identity.version !== null &&
|
|
307
|
+
flowRefs.has(entityRefKey(identity.id, identity.version)));
|
|
308
|
+
}).length;
|
|
309
|
+
}
|
|
310
|
+
function exchangeClosureKeys(rows, flowRefs) {
|
|
311
|
+
const keys = new Set();
|
|
312
|
+
for (const row of rows.filter((entry) => entry.table === 'processes' && entry.json_ordered)) {
|
|
313
|
+
for (const [index, exchange] of (processExchangeEntries(row.json_ordered) ?? []).entries()) {
|
|
314
|
+
const identity = referenceIdentity(exchange.referenceToFlowDataSet);
|
|
315
|
+
if (identity.id &&
|
|
316
|
+
identity.version &&
|
|
317
|
+
flowRefs.has(entityRefKey(identity.id, identity.version))) {
|
|
318
|
+
keys.add(`${row.id}\u0000${row.version}\u0000${index}\u0000${String(exchange['@dataSetInternalID'])}\u0000${identity.id}\u0000${identity.version}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return keys;
|
|
323
|
+
}
|
|
324
|
+
function selectorClosureKeys(actions) {
|
|
325
|
+
return new Set(actions.flatMap((action) => (action.exchange_instances ?? []).map((instance) => `${action.id}\u0000${action.version}\u0000${instance.exchange_index}\u0000${instance.data_set_internal_id}\u0000${instance.flow_id}\u0000${instance.flow_version}`)));
|
|
326
|
+
}
|
|
327
|
+
function projectRows(rows, desiredPayloads, actions) {
|
|
328
|
+
const actionByKey = new Map(actions.map((action) => [maintenanceRowKey(action), action]));
|
|
329
|
+
return rows.map((row) => {
|
|
330
|
+
const action = actionByKey.get(maintenanceRowKey(row));
|
|
331
|
+
const desired = action ? desiredPayloads.get(action.action_id) : null;
|
|
332
|
+
return desired ? { ...row, json_ordered: desired } : row;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
export function buildAliasRewritePlan(options) {
|
|
336
|
+
const batches = options.scope.alias_batches ?? [];
|
|
337
|
+
const schemas = options.schemas ?? DEFAULT_ALIAS_SCHEMAS;
|
|
338
|
+
const desiredPayloads = new Map();
|
|
339
|
+
const batchPlans = [];
|
|
340
|
+
for (const batch of batches) {
|
|
341
|
+
const profile = ALIAS_PROFILES[batch.dimension];
|
|
342
|
+
const actions = options.actions.filter((action) => action.batch_id === batch.batch_id);
|
|
343
|
+
const byTable = {
|
|
344
|
+
flowproperties: actions.filter((action) => action.table === 'flowproperties'),
|
|
345
|
+
flows: actions.filter((action) => action.table === 'flows'),
|
|
346
|
+
processes: actions.filter((action) => action.table === 'processes'),
|
|
347
|
+
};
|
|
348
|
+
const targetSnapshots = options.targetSnapshots.get(batch.batch_id) ?? {
|
|
349
|
+
unitgroup: null,
|
|
350
|
+
flowproperty: null,
|
|
351
|
+
source_unitgroup: null,
|
|
352
|
+
};
|
|
353
|
+
const targetUnitGroupReference = targetUnitGroupReferenceFromFlowProperty(targetSnapshots.flowproperty, batch);
|
|
354
|
+
const targetFlowPropertyReference = canonicalFlowPropertyReference(options.accountRows, batch);
|
|
355
|
+
if (actions.length !== profile.rows ||
|
|
356
|
+
byTable.flowproperties.length !== profile.flowproperties ||
|
|
357
|
+
byTable.flows.length !== profile.flows ||
|
|
358
|
+
byTable.processes.length !== profile.processes) {
|
|
359
|
+
addBatchBlocker(options.actions, batch.batch_id, 'ALIAS_BATCH_ROW_COUNTS_MISMATCH', 'Alias batch row/table counts do not match the frozen dimension profile.', {
|
|
360
|
+
profile,
|
|
361
|
+
observed: {
|
|
362
|
+
rows: actions.length,
|
|
363
|
+
...Object.fromEntries(Object.entries(byTable).map(([key, value]) => [key, value.length])),
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (byTable.flowproperties[0]?.id !== batch.source.flowproperty.id ||
|
|
368
|
+
byTable.flowproperties[0]?.version !== batch.source.flowproperty.version) {
|
|
369
|
+
addBatchBlocker(options.actions, batch.batch_id, 'ALIAS_SOURCE_FLOWPROPERTY_ACTION_MISMATCH', 'The one flowproperty action must target the frozen source flowproperty.');
|
|
370
|
+
}
|
|
371
|
+
if (!targetSnapshotValid(targetSnapshots.unitgroup, 'unitgroups', batch, options.scope.account.user_id) ||
|
|
372
|
+
!targetSnapshotValid(targetSnapshots.flowproperty, 'flowproperties', batch, options.scope.account.user_id) ||
|
|
373
|
+
!sourceUnitGroupSnapshotValid(targetSnapshots.source_unitgroup, batch, options.scope.account.user_id)) {
|
|
374
|
+
addBatchBlocker(options.actions, batch.batch_id, 'ALIAS_SUPPORT_NOT_OWNER_DRAFT', 'Source and target FP/UG support must be exact current-owner state_code=0 drafts.');
|
|
375
|
+
}
|
|
376
|
+
const referenceUnit = sourceReferenceUnit(targetSnapshots.source_unitgroup, batch);
|
|
377
|
+
const conversionUnit = targetConversionUnit({
|
|
378
|
+
source: referenceUnit,
|
|
379
|
+
target: targetSnapshots.unitgroup,
|
|
380
|
+
factor: batch.factor,
|
|
381
|
+
});
|
|
382
|
+
if (!targetUnitGroupReference ||
|
|
383
|
+
!targetFlowPropertyReference ||
|
|
384
|
+
!referenceUnit ||
|
|
385
|
+
!conversionUnit) {
|
|
386
|
+
addBatchBlocker(options.actions, batch.batch_id, 'ALIAS_TARGET_REFERENCE_INVALID', 'Target references could not be derived from the frozen owner-draft support rows.');
|
|
387
|
+
}
|
|
388
|
+
const exchangeRewrites = [];
|
|
389
|
+
for (const action of actions) {
|
|
390
|
+
const payload = action.before?.json_ordered;
|
|
391
|
+
let desired = null;
|
|
392
|
+
if (payload && action.table === 'flowproperties' && targetUnitGroupReference) {
|
|
393
|
+
desired = replaceAliasFlowProperty(payload, batch, targetUnitGroupReference);
|
|
394
|
+
}
|
|
395
|
+
else if (payload && action.table === 'flows' && targetFlowPropertyReference) {
|
|
396
|
+
desired = replaceFlowReferenceProperty(payload, batch, targetFlowPropertyReference);
|
|
397
|
+
}
|
|
398
|
+
else if (payload && action.table === 'processes') {
|
|
399
|
+
const result = rewriteProcessExchanges({ payload, action, batch });
|
|
400
|
+
desired = result?.payload ?? null;
|
|
401
|
+
exchangeRewrites.push(...(result?.rewrites ?? []));
|
|
402
|
+
}
|
|
403
|
+
const schema = action.table === 'flowproperties'
|
|
404
|
+
? schemas.flowproperties
|
|
405
|
+
: action.table === 'flows'
|
|
406
|
+
? schemas.flows
|
|
407
|
+
: schemas.processes;
|
|
408
|
+
if (!desired ||
|
|
409
|
+
(payload && sha256Json(desired) === sha256Json(payload)) ||
|
|
410
|
+
!schema.safeParse(desired).success) {
|
|
411
|
+
action.blockers.push(aliasBlocker(action, 'ALIAS_DESIRED_PAYLOAD_INVALID', 'The exact authorized alias rewrite could not be generated from the frozen row.'));
|
|
412
|
+
action.status = 'blocked';
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
desiredPayloads.set(action.action_id, desired);
|
|
416
|
+
if (action.table === 'flowproperties') {
|
|
417
|
+
action.alias_mutation = { kind: 'flowproperty_unitgroup_reference' };
|
|
418
|
+
}
|
|
419
|
+
else if (action.table === 'flows') {
|
|
420
|
+
const entry = flowPropertySingleton(payload);
|
|
421
|
+
const internalId = entry?.['@dataSetInternalID'];
|
|
422
|
+
if (typeof internalId === 'string') {
|
|
423
|
+
action.alias_mutation = {
|
|
424
|
+
kind: 'flow_flowproperty_reference',
|
|
425
|
+
flow_property_internal_id: internalId,
|
|
426
|
+
source_flowproperty_id: batch.source.flowproperty.id,
|
|
427
|
+
source_flowproperty_version: batch.source.flowproperty.version,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
action.alias_mutation = {
|
|
433
|
+
kind: 'process_exchange_amounts',
|
|
434
|
+
exchanges: action.exchange_instances.map((instance) => ({
|
|
435
|
+
index: instance.exchange_index,
|
|
436
|
+
internal_id: instance.data_set_internal_id,
|
|
437
|
+
flow_id: instance.flow_id,
|
|
438
|
+
flow_version: instance.flow_version,
|
|
439
|
+
direction: instance.direction,
|
|
440
|
+
before_exchange_sha256: instance.before_exchange_sha256,
|
|
441
|
+
})),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const sourceFlowIds = new Set(byTable.flows.map((action) => entityRefKey(action.id, action.version)));
|
|
447
|
+
const observedSourceFlowIds = flowsWithProperty(options.accountRows, batch.source.flowproperty.id, batch.source.flowproperty.version);
|
|
448
|
+
const observedClosure = exchangeClosureKeys(options.accountRows, sourceFlowIds);
|
|
449
|
+
const selectedClosure = selectorClosureKeys(byTable.processes);
|
|
450
|
+
if (sha256Json([...sourceFlowIds].sort()) !== sha256Json([...observedSourceFlowIds].sort()) ||
|
|
451
|
+
sha256Json([...observedClosure].sort()) !== sha256Json([...selectedClosure].sort())) {
|
|
452
|
+
addBatchBlocker(options.actions, batch.batch_id, 'ALIAS_REFERENCE_CLOSURE_MISMATCH', 'Flow actions and frozen exchange selectors do not exactly cover the source alias closure.', {
|
|
453
|
+
action_flow_ids: [...sourceFlowIds].sort(),
|
|
454
|
+
observed_flow_ids: [...observedSourceFlowIds].sort(),
|
|
455
|
+
observed_exchanges: observedClosure.size,
|
|
456
|
+
selected_exchanges: selectedClosure.size,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (exchangeRewrites.length !== profile.exchanges) {
|
|
460
|
+
addBatchBlocker(options.actions, batch.batch_id, 'ALIAS_EXCHANGE_COUNT_MISMATCH', 'Scaled exchange count does not match the frozen dimension profile.', { expected: profile.exchanges, actual: exchangeRewrites.length });
|
|
461
|
+
}
|
|
462
|
+
const processExchangeCount = byTable.processes.reduce((sum, action) => sum + (processExchangeEntries(action.before?.json_ordered ?? {})?.length ?? 0), 0);
|
|
463
|
+
batchPlans.push({
|
|
464
|
+
...batch,
|
|
465
|
+
action_ids: actions.map((action) => action.action_id),
|
|
466
|
+
target_snapshots: targetSnapshots,
|
|
467
|
+
conversion_evidence: {
|
|
468
|
+
source_unitgroup_payload_sha256: targetSnapshots.source_unitgroup?.payload_sha256 ?? null,
|
|
469
|
+
source_reference_unit: referenceUnit,
|
|
470
|
+
target_conversion_unit: conversionUnit,
|
|
471
|
+
},
|
|
472
|
+
exchange_rewrites: exchangeRewrites,
|
|
473
|
+
summary: {
|
|
474
|
+
rows: actions.length,
|
|
475
|
+
flowproperties: byTable.flowproperties.length,
|
|
476
|
+
flows: byTable.flows.length,
|
|
477
|
+
processes: byTable.processes.length,
|
|
478
|
+
exchanges: exchangeRewrites.length,
|
|
479
|
+
amount_fields: exchangeRewrites.length * 2,
|
|
480
|
+
unrelated_exchanges: processExchangeCount - exchangeRewrites.length,
|
|
481
|
+
},
|
|
482
|
+
postconditions: {
|
|
483
|
+
source_unitgroup_incoming_refs: -1,
|
|
484
|
+
source_flowproperty_flow_refs: -1,
|
|
485
|
+
target_flow_refs: -1,
|
|
486
|
+
target_exchange_refs: -1,
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
const projected = projectRows(options.accountRows, desiredPayloads, options.actions);
|
|
491
|
+
for (const batchPlan of batchPlans) {
|
|
492
|
+
const targetFlows = flowsWithProperty(projected, batchPlan.target.flowproperty.id, batchPlan.target.flowproperty.version);
|
|
493
|
+
batchPlan.postconditions = {
|
|
494
|
+
source_unitgroup_incoming_refs: countUnitGroupRefs(projected, batchPlan.source.unitgroup.id, batchPlan.source.unitgroup.version),
|
|
495
|
+
source_flowproperty_flow_refs: countFlowPropertyRefs(projected, batchPlan.source.flowproperty.id, batchPlan.source.flowproperty.version),
|
|
496
|
+
target_flow_refs: targetFlows.size,
|
|
497
|
+
target_exchange_refs: countExchangeFlowRefs(projected, targetFlows),
|
|
498
|
+
};
|
|
499
|
+
const profile = ALIAS_PROFILES[batchPlan.dimension];
|
|
500
|
+
if (batchPlan.postconditions.source_unitgroup_incoming_refs !== 0 ||
|
|
501
|
+
batchPlan.postconditions.source_flowproperty_flow_refs !== 0 ||
|
|
502
|
+
batchPlan.postconditions.target_flow_refs !== profile.target_flow_refs ||
|
|
503
|
+
batchPlan.postconditions.target_exchange_refs !== profile.target_exchange_refs) {
|
|
504
|
+
addBatchBlocker(options.actions, batchPlan.batch_id, 'ALIAS_POSTCONDITIONS_MISMATCH', 'Projected alias reference counts do not match the frozen postconditions.', { expected: profile, actual: batchPlan.postconditions });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const unrelated = batchPlans.reduce((sum, batch) => sum + batch.summary.unrelated_exchanges, 0);
|
|
508
|
+
if (unrelated !== EXPECTED_UNRELATED_EXCHANGES) {
|
|
509
|
+
addBatchBlocker(options.actions, batchPlans[0]?.batch_id ?? '', 'ALIAS_UNRELATED_EXCHANGE_COUNT_MISMATCH', 'The exact process closure must preserve 309 unrelated exchanges.', { expected: EXPECTED_UNRELATED_EXCHANGES, actual: unrelated });
|
|
510
|
+
}
|
|
511
|
+
return { desired_payloads: desiredPayloads, batches: batchPlans };
|
|
512
|
+
}
|
|
513
|
+
export const __testInternals = {
|
|
514
|
+
ALIAS_PROFILES,
|
|
515
|
+
EXPECTED_UNRELATED_EXCHANGES,
|
|
516
|
+
countExchangeFlowRefs,
|
|
517
|
+
countFlowPropertyRefs,
|
|
518
|
+
countUnitGroupRefs,
|
|
519
|
+
decimalEqual,
|
|
520
|
+
decimalParts,
|
|
521
|
+
decimalText,
|
|
522
|
+
exchangeClosureKeys,
|
|
523
|
+
flowPropertyEntries,
|
|
524
|
+
flowPropertySingleton,
|
|
525
|
+
flowsWithProperty,
|
|
526
|
+
processExchangeEntries,
|
|
527
|
+
projectRows,
|
|
528
|
+
referenceIdentity,
|
|
529
|
+
referenceMatches,
|
|
530
|
+
replaceAliasFlowProperty,
|
|
531
|
+
replaceFlowReferenceProperty,
|
|
532
|
+
rewriteProcessExchanges,
|
|
533
|
+
selectorClosureKeys,
|
|
534
|
+
targetUnitGroupReferenceFromFlowProperty,
|
|
535
|
+
canonicalFlowPropertyReference,
|
|
536
|
+
targetSnapshotValid,
|
|
537
|
+
sourceReferenceUnit,
|
|
538
|
+
sourceUnitGroupSnapshotValid,
|
|
539
|
+
targetConversionUnit,
|
|
540
|
+
};
|
|
541
|
+
//# sourceMappingURL=dataset-maintenance-alias-rewrite.js.map
|