@tiangong-lca/cli 0.0.32 → 0.0.33
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 +5 -0
- package/dist/src/cli.js +20 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-apply.js +713 -7
- package/dist/src/lib/dataset-maintenance-apply.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-remote.js +34 -0
- package/dist/src/lib/dataset-maintenance-remote.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { closeSync, existsSync, fchmodSync, fsyncSync, openSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { writeJsonArtifact } from './artifacts.js';
|
|
4
|
+
import { collectRemoteReferences } from './dataset-remote-verify.js';
|
|
4
5
|
import { CliError } from './errors.js';
|
|
5
6
|
import { withStateFileLock } from './state-lock.js';
|
|
6
7
|
import { buildAliasBatchRequest, buildAliasPlanRequest, loadMaintenanceDesiredPayload, orderedAliasBatches, } from './dataset-maintenance-alias-request.js';
|
|
7
|
-
import { MAINTENANCE_SCAN_TABLES, appendStableJsonLine, isJsonObject, maintenanceRowKey, parseMaintenancePlan, readJsonFile, readJsonLinesIfPresent, sha256Json, snapshotRemoteRow, writeImmutableJson, } from './dataset-maintenance-contract.js';
|
|
8
|
+
import { MAINTENANCE_SCAN_TABLES, appendStableJsonLine, isJsonObject, maintenanceRowKey, parseMaintenancePlan, readJsonFile, readJsonLinesIfPresent, sha256Json, snapshotRemoteRow, stableJsonText, writeImmutableJson, } from './dataset-maintenance-contract.js';
|
|
8
9
|
import { buildDerivativePlanRequest, derivativePlanAction, parseDerivativeSnapshotResponse, parseDerivativeSubmitResponse, } from './dataset-maintenance-derivatives.js';
|
|
9
10
|
import { maintenanceProjectedReferenceFingerprint } from './dataset-maintenance-plan.js';
|
|
10
11
|
import { isSnapshotCompletenessCompatible } from './dataset-maintenance-pagination.js';
|
|
11
|
-
import { applyMaintenanceAliasPlan, applyMaintenanceDerivativeRebuild, deleteMaintenanceRow, fetchMaintenanceAccountRows, fetchMaintenanceDerivativeSnapshot, fetchMaintenanceExactRows, resolveMaintenanceRemoteContext, saveDraftMaintenanceRow, } from './dataset-maintenance-remote.js';
|
|
12
|
+
import { applyMaintenanceAliasPlan, applyMaintenanceDerivativeRebuild, deleteMaintenanceRow, fetchMaintenanceAccountRows, fetchMaintenanceDerivativeSnapshot, fetchMaintenanceExactRows, fetchMaintenanceVisibleTableRows, resolveMaintenanceRemoteContext, saveDraftMaintenanceRow, } from './dataset-maintenance-remote.js';
|
|
12
13
|
const POSITIVE_INTEGER_TEXT = /^[1-9]\d*$/u;
|
|
13
14
|
function clock(options) {
|
|
14
15
|
return (options.now ?? new Date()).toISOString();
|
|
@@ -16,6 +17,252 @@ function clock(options) {
|
|
|
16
17
|
function errorMessage(error) {
|
|
17
18
|
return error instanceof Error ? error.message : String(error);
|
|
18
19
|
}
|
|
20
|
+
const ABSENT_READBACK_SHA256 = sha256Json([]);
|
|
21
|
+
function parallelDeleteDesiredSha256(action) {
|
|
22
|
+
return sha256Json({
|
|
23
|
+
action: 'delete',
|
|
24
|
+
table: action.table,
|
|
25
|
+
id: action.id,
|
|
26
|
+
version: action.version,
|
|
27
|
+
desired: 'absent',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function normalizeMaintenanceMaxParallel(value) {
|
|
31
|
+
const normalized = value ?? 1;
|
|
32
|
+
if (!Number.isInteger(normalized) || normalized < 1 || normalized > 8) {
|
|
33
|
+
throw new CliError('--max-parallel must be an integer from 1 to 8.', {
|
|
34
|
+
code: 'DATASET_MAINTENANCE_MAX_PARALLEL_INVALID',
|
|
35
|
+
exitCode: 2,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return normalized;
|
|
39
|
+
}
|
|
40
|
+
function assertParallelDeletePlan(plan) {
|
|
41
|
+
const targets = new Set();
|
|
42
|
+
if (plan.operation !== 'delete' ||
|
|
43
|
+
plan.actions.length === 0 ||
|
|
44
|
+
plan.actions.some((action) => action.action !== 'delete' || action.table !== 'flows') ||
|
|
45
|
+
plan.summary.delete !== plan.actions.length ||
|
|
46
|
+
plan.summary.save_draft !== 0 ||
|
|
47
|
+
(plan.summary.update_json_ordered ?? 0) !== 0 ||
|
|
48
|
+
(plan.summary.rebuild_derivatives ?? 0) !== 0 ||
|
|
49
|
+
plan.summary.current_reference_impacts !== 0 ||
|
|
50
|
+
plan.summary.projected_reference_impacts !== 0) {
|
|
51
|
+
throw new CliError('--max-parallel maintenance apply requires a non-empty flow delete-only plan with zero current and projected inbound references.', {
|
|
52
|
+
code: 'DATASET_MAINTENANCE_PARALLEL_DELETE_PLAN_REQUIRED',
|
|
53
|
+
exitCode: 1,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
for (const action of plan.actions) {
|
|
57
|
+
const target = `${action.table}\u0000${action.id}\u0000${action.version}`;
|
|
58
|
+
if (targets.has(target)) {
|
|
59
|
+
throw new CliError('Parallel delete plan contains a repeated table/id/version target.', {
|
|
60
|
+
code: 'DATASET_MAINTENANCE_PARALLEL_DELETE_TARGET_DUPLICATE',
|
|
61
|
+
exitCode: 1,
|
|
62
|
+
details: { table: action.table, id: action.id, version: action.version },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
targets.add(target);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function parseParallelDeleteExecutionLog(plan, executionLogPath) {
|
|
69
|
+
const actions = new Map(plan.actions.map((action) => [action.action_id, action]));
|
|
70
|
+
const entries = [];
|
|
71
|
+
const byAction = new Map();
|
|
72
|
+
const dispatched = new Set();
|
|
73
|
+
const committed = new Set();
|
|
74
|
+
for (const value of readJsonLinesIfPresent(executionLogPath)) {
|
|
75
|
+
const action = isJsonObject(value) && typeof value.action_id === 'string'
|
|
76
|
+
? actions.get(value.action_id)
|
|
77
|
+
: null;
|
|
78
|
+
const expectedDesired = action ? parallelDeleteDesiredSha256(action) : null;
|
|
79
|
+
const actionEntries = action ? (byAction.get(action.action_id) ?? []) : [];
|
|
80
|
+
const priorDispatched = actionEntries.some((entry) => entry.status === 'DISPATCHED');
|
|
81
|
+
const priorCommitted = actionEntries.some((entry) => entry.status === 'COMMITTED');
|
|
82
|
+
const status = isJsonObject(value) ? value.status : null;
|
|
83
|
+
const auditIdValid = isJsonObject(value) &&
|
|
84
|
+
(value.audit_id === null ||
|
|
85
|
+
(typeof value.audit_id === 'string' && POSITIVE_INTEGER_TEXT.test(value.audit_id)));
|
|
86
|
+
const remoteHashValid = isJsonObject(value) &&
|
|
87
|
+
(value.remote_result_sha256 === null ||
|
|
88
|
+
(typeof value.remote_result_sha256 === 'string' &&
|
|
89
|
+
/^[a-f0-9]{64}$/u.test(value.remote_result_sha256)));
|
|
90
|
+
const commonOutcomeFieldsValid = isJsonObject(value) &&
|
|
91
|
+
typeof value.attempt_consumed === 'boolean' &&
|
|
92
|
+
typeof value.recovered === 'boolean' &&
|
|
93
|
+
(value.readback_sha256 === null ||
|
|
94
|
+
(typeof value.readback_sha256 === 'string' &&
|
|
95
|
+
/^[a-f0-9]{64}$/u.test(value.readback_sha256))) &&
|
|
96
|
+
(value.error === null || typeof value.error === 'string');
|
|
97
|
+
const statusFieldsValid = isJsonObject(value) &&
|
|
98
|
+
((status === 'PREPARED' &&
|
|
99
|
+
value.attempt_consumed === false &&
|
|
100
|
+
value.recovered === false &&
|
|
101
|
+
value.audit_id === null &&
|
|
102
|
+
value.readback_sha256 === null &&
|
|
103
|
+
value.remote_result_sha256 === null &&
|
|
104
|
+
value.error === null &&
|
|
105
|
+
!priorDispatched &&
|
|
106
|
+
!priorCommitted) ||
|
|
107
|
+
(status === 'DISPATCHED' &&
|
|
108
|
+
value.attempt_consumed === true &&
|
|
109
|
+
value.recovered === false &&
|
|
110
|
+
value.audit_id === null &&
|
|
111
|
+
value.readback_sha256 === null &&
|
|
112
|
+
value.remote_result_sha256 === null &&
|
|
113
|
+
value.error === null &&
|
|
114
|
+
actionEntries.some((entry) => entry.status === 'PREPARED') &&
|
|
115
|
+
!priorDispatched &&
|
|
116
|
+
!priorCommitted) ||
|
|
117
|
+
(status === 'UNKNOWN' &&
|
|
118
|
+
value.attempt_consumed === true &&
|
|
119
|
+
typeof value.error === 'string' &&
|
|
120
|
+
value.error.length > 0 &&
|
|
121
|
+
priorDispatched &&
|
|
122
|
+
!priorCommitted) ||
|
|
123
|
+
(status === 'COMMITTED' &&
|
|
124
|
+
value.attempt_consumed === true &&
|
|
125
|
+
value.readback_sha256 === ABSENT_READBACK_SHA256 &&
|
|
126
|
+
value.error === null &&
|
|
127
|
+
priorDispatched &&
|
|
128
|
+
!priorCommitted));
|
|
129
|
+
if (!isJsonObject(value) ||
|
|
130
|
+
value.schema_version !== 1 ||
|
|
131
|
+
value.plan_sha256 !== plan.plan_sha256 ||
|
|
132
|
+
value.operation_id !== plan.operation_id ||
|
|
133
|
+
!action ||
|
|
134
|
+
action.action !== 'delete' ||
|
|
135
|
+
action.table !== 'flows' ||
|
|
136
|
+
value.attempt_key !== `${action.action_id}@${expectedDesired}` ||
|
|
137
|
+
value.action !== 'delete' ||
|
|
138
|
+
value.table !== 'flows' ||
|
|
139
|
+
value.id !== action.id ||
|
|
140
|
+
value.version !== action.version ||
|
|
141
|
+
value.desired_sha256 !== expectedDesired ||
|
|
142
|
+
value.before_sha256 !== action.before?.row_sha256 ||
|
|
143
|
+
!isJsonObject(value.actor) ||
|
|
144
|
+
value.actor.user_id !== plan.account.user_id ||
|
|
145
|
+
value.actor.email !== plan.account.email ||
|
|
146
|
+
!isJsonObject(value.audit_context) ||
|
|
147
|
+
value.audit_context.plan_sha256 !== plan.plan_sha256 ||
|
|
148
|
+
value.audit_context.operation_id !== plan.operation_id ||
|
|
149
|
+
value.audit_context.action_id !== action.action_id ||
|
|
150
|
+
value.audit_context.reason_code !== action.reason_code ||
|
|
151
|
+
value.audit_context.source !== 'tiangong-lca dataset maintenance apply' ||
|
|
152
|
+
typeof value.recorded_at_utc !== 'string' ||
|
|
153
|
+
!Number.isFinite(Date.parse(value.recorded_at_utc)) ||
|
|
154
|
+
!auditIdValid ||
|
|
155
|
+
!remoteHashValid ||
|
|
156
|
+
!commonOutcomeFieldsValid ||
|
|
157
|
+
!statusFieldsValid) {
|
|
158
|
+
throw new CliError('Parallel delete execution log contains an invalid or foreign entry.', {
|
|
159
|
+
code: 'DATASET_MAINTENANCE_PARALLEL_DELETE_LOG_INVALID',
|
|
160
|
+
exitCode: 1,
|
|
161
|
+
details: value,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const entry = value;
|
|
165
|
+
entries.push(entry);
|
|
166
|
+
actionEntries.push(entry);
|
|
167
|
+
byAction.set(action.action_id, actionEntries);
|
|
168
|
+
if (entry.status === 'DISPATCHED')
|
|
169
|
+
dispatched.add(action.action_id);
|
|
170
|
+
if (entry.status === 'COMMITTED')
|
|
171
|
+
committed.add(action.action_id);
|
|
172
|
+
}
|
|
173
|
+
return { entries, byAction, dispatched, committed };
|
|
174
|
+
}
|
|
175
|
+
function appendParallelDeleteExecutionEntry(options) {
|
|
176
|
+
const desiredSha256 = parallelDeleteDesiredSha256(options.action);
|
|
177
|
+
const entry = {
|
|
178
|
+
schema_version: 1,
|
|
179
|
+
plan_sha256: options.plan.plan_sha256,
|
|
180
|
+
operation_id: options.plan.operation_id,
|
|
181
|
+
action_id: options.action.action_id,
|
|
182
|
+
attempt_key: `${options.action.action_id}@${desiredSha256}`,
|
|
183
|
+
action: 'delete',
|
|
184
|
+
table: 'flows',
|
|
185
|
+
id: options.action.id,
|
|
186
|
+
version: options.action.version,
|
|
187
|
+
desired_sha256: desiredSha256,
|
|
188
|
+
before_sha256: options.action.before.row_sha256,
|
|
189
|
+
actor: {
|
|
190
|
+
user_id: options.context.account.user_id,
|
|
191
|
+
email: options.context.account.email,
|
|
192
|
+
},
|
|
193
|
+
status: options.status,
|
|
194
|
+
recorded_at_utc: options.recordedAtUtc,
|
|
195
|
+
attempt_consumed: options.status !== 'PREPARED',
|
|
196
|
+
recovered: options.recovered ?? false,
|
|
197
|
+
audit_context: {
|
|
198
|
+
plan_sha256: options.plan.plan_sha256,
|
|
199
|
+
operation_id: options.plan.operation_id,
|
|
200
|
+
action_id: options.action.action_id,
|
|
201
|
+
reason_code: options.action.reason_code,
|
|
202
|
+
source: 'tiangong-lca dataset maintenance apply',
|
|
203
|
+
},
|
|
204
|
+
audit_id: options.auditId ?? null,
|
|
205
|
+
readback_sha256: options.readbackSha256 ?? null,
|
|
206
|
+
remote_result_sha256: options.remoteResultSha256 ?? null,
|
|
207
|
+
error: options.error ?? null,
|
|
208
|
+
};
|
|
209
|
+
const descriptor = openSync(options.path, 'a', 0o600);
|
|
210
|
+
try {
|
|
211
|
+
fchmodSync(descriptor, 0o600);
|
|
212
|
+
writeFileSync(descriptor, `${stableJsonText(entry)}\n`, 'utf8');
|
|
213
|
+
fsyncSync(descriptor);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
closeSync(descriptor);
|
|
217
|
+
}
|
|
218
|
+
options.state.entries.push(entry);
|
|
219
|
+
const actionEntries = options.state.byAction.get(options.action.action_id) ?? [];
|
|
220
|
+
actionEntries.push(entry);
|
|
221
|
+
options.state.byAction.set(options.action.action_id, actionEntries);
|
|
222
|
+
if (entry.status === 'DISPATCHED')
|
|
223
|
+
options.state.dispatched.add(options.action.action_id);
|
|
224
|
+
if (entry.status === 'COMMITTED')
|
|
225
|
+
options.state.committed.add(options.action.action_id);
|
|
226
|
+
return entry;
|
|
227
|
+
}
|
|
228
|
+
function assertNoVisibleProcessInboundReferences(options) {
|
|
229
|
+
const targets = new Set(options.plan.actions.map((action) => `${action.id}\u0000${action.version}`));
|
|
230
|
+
const targetIds = new Set(options.plan.actions.map((action) => action.id));
|
|
231
|
+
const payloadRows = options.rows
|
|
232
|
+
.filter((row) => row.table === 'processes' && row.json_ordered !== null)
|
|
233
|
+
.map((row) => ({
|
|
234
|
+
table: row.table,
|
|
235
|
+
id: row.id,
|
|
236
|
+
version: row.version,
|
|
237
|
+
json_ordered: row.json_ordered,
|
|
238
|
+
}));
|
|
239
|
+
const references = collectRemoteReferences(payloadRows).filter((reference) => reference.role === 'reference' &&
|
|
240
|
+
reference.table === 'flows' &&
|
|
241
|
+
typeof reference.id === 'string');
|
|
242
|
+
const inbound = references.filter((reference) => reference.version
|
|
243
|
+
? targets.has(`${reference.id}\u0000${reference.version}`)
|
|
244
|
+
: targetIds.has(reference.id));
|
|
245
|
+
if (inbound.length > 0) {
|
|
246
|
+
throw new CliError('Parallel flow delete admission found visible process inbound references.', {
|
|
247
|
+
code: 'DATASET_MAINTENANCE_PARALLEL_DELETE_INBOUND_REFERENCES',
|
|
248
|
+
exitCode: 1,
|
|
249
|
+
details: {
|
|
250
|
+
inbound_count: inbound.length,
|
|
251
|
+
first: inbound.slice(0, 20),
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
process_rows: payloadRows.length,
|
|
257
|
+
process_references: references.length,
|
|
258
|
+
snapshot_sha256: sha256Json(payloadRows.map((row) => ({
|
|
259
|
+
table: row.table,
|
|
260
|
+
id: row.id,
|
|
261
|
+
version: row.version,
|
|
262
|
+
payload_sha256: sha256Json(row.json_ordered),
|
|
263
|
+
}))),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
19
266
|
function loadDesiredPayload(planDir, action) {
|
|
20
267
|
return loadMaintenanceDesiredPayload(planDir, action);
|
|
21
268
|
}
|
|
@@ -413,6 +660,13 @@ function assertApplyPreconditions(options) {
|
|
|
413
660
|
}
|
|
414
661
|
continue;
|
|
415
662
|
}
|
|
663
|
+
if (action.action === 'delete' && options.attemptedDeleteActionIds?.has(action.action_id)) {
|
|
664
|
+
// A request may have committed before its response or progress row was
|
|
665
|
+
// persisted, or the target may have drifted after dispatch. The execution
|
|
666
|
+
// ledger owns both cases and permits only read-only recovery, never an
|
|
667
|
+
// automatic replay.
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
416
670
|
if (!currentRow || currentRow.user_id !== action.expected_user_id) {
|
|
417
671
|
throw new CliError(`Action row is missing, non-draft, or not owned: ${action.action_id}`, {
|
|
418
672
|
code: 'DATASET_MAINTENANCE_ACTION_ROW_DRIFT',
|
|
@@ -1026,6 +1280,335 @@ async function executeAction(options) {
|
|
|
1026
1280
|
}
|
|
1027
1281
|
return { afterSha256: null, remoteResultSha256: sha256Json(remoteResult) };
|
|
1028
1282
|
}
|
|
1283
|
+
function appendParallelDeleteProgress(options) {
|
|
1284
|
+
const entry = {
|
|
1285
|
+
schema_version: 1,
|
|
1286
|
+
plan_sha256: options.plan.plan_sha256,
|
|
1287
|
+
operation_id: options.plan.operation_id,
|
|
1288
|
+
action_id: options.action.action_id,
|
|
1289
|
+
action: 'delete',
|
|
1290
|
+
table: options.action.table,
|
|
1291
|
+
id: options.action.id,
|
|
1292
|
+
version: options.action.version,
|
|
1293
|
+
reason_code: options.action.reason_code,
|
|
1294
|
+
audit_context: {
|
|
1295
|
+
plan_sha256: options.plan.plan_sha256,
|
|
1296
|
+
operation_id: options.plan.operation_id,
|
|
1297
|
+
action_id: options.action.action_id,
|
|
1298
|
+
reason_code: options.action.reason_code,
|
|
1299
|
+
source: 'tiangong-lca dataset maintenance apply',
|
|
1300
|
+
},
|
|
1301
|
+
actor: {
|
|
1302
|
+
user_id: options.context.account.user_id,
|
|
1303
|
+
email: options.context.account.email,
|
|
1304
|
+
},
|
|
1305
|
+
started_at_utc: options.startedAtUtc,
|
|
1306
|
+
ended_at_utc: options.endedAtUtc,
|
|
1307
|
+
before_sha256: options.action.before.row_sha256,
|
|
1308
|
+
after_sha256: null,
|
|
1309
|
+
remote_result_sha256: options.remoteResultSha256,
|
|
1310
|
+
result: options.result,
|
|
1311
|
+
error: options.error,
|
|
1312
|
+
rollback: options.action.rollback,
|
|
1313
|
+
};
|
|
1314
|
+
appendStableJsonLine(options.progressPath, entry);
|
|
1315
|
+
options.progress.entries.push(entry);
|
|
1316
|
+
if (entry.result === 'success') {
|
|
1317
|
+
options.progress.successes.set(entry.action_id, entry);
|
|
1318
|
+
options.progress.latestFailures.delete(entry.action_id);
|
|
1319
|
+
}
|
|
1320
|
+
else if (!options.progress.successes.has(entry.action_id)) {
|
|
1321
|
+
options.progress.latestFailures.set(entry.action_id, entry);
|
|
1322
|
+
}
|
|
1323
|
+
return entry;
|
|
1324
|
+
}
|
|
1325
|
+
function remoteAuditId(value) {
|
|
1326
|
+
const candidate = value.audit_id;
|
|
1327
|
+
return typeof candidate === 'string' && POSITIVE_INTEGER_TEXT.test(candidate) ? candidate : null;
|
|
1328
|
+
}
|
|
1329
|
+
async function exactParallelDeleteRows(options) {
|
|
1330
|
+
return (await fetchMaintenanceExactRows({
|
|
1331
|
+
context: options.context,
|
|
1332
|
+
table: options.action.table,
|
|
1333
|
+
id: options.action.id,
|
|
1334
|
+
version: options.action.version,
|
|
1335
|
+
})).rows;
|
|
1336
|
+
}
|
|
1337
|
+
function exactParallelDeleteBefore(options) {
|
|
1338
|
+
const row = options.rows[0];
|
|
1339
|
+
return Boolean(options.rows.length === 1 &&
|
|
1340
|
+
row &&
|
|
1341
|
+
row.user_id === options.action.expected_user_id &&
|
|
1342
|
+
row.state_code === 0 &&
|
|
1343
|
+
snapshotRemoteRow(row).row_sha256 === options.action.before?.row_sha256);
|
|
1344
|
+
}
|
|
1345
|
+
async function executeParallelDeletePlan(options) {
|
|
1346
|
+
const statuses = new Map();
|
|
1347
|
+
const recoverAttempted = async (action) => {
|
|
1348
|
+
const startedAt = options.now();
|
|
1349
|
+
let rows;
|
|
1350
|
+
try {
|
|
1351
|
+
rows = await exactParallelDeleteRows({ context: options.context, action });
|
|
1352
|
+
}
|
|
1353
|
+
catch (error) {
|
|
1354
|
+
statuses.set(action.action_id, 'unknown');
|
|
1355
|
+
if (options.execution.byAction.get(action.action_id)?.at(-1)?.status !== 'UNKNOWN') {
|
|
1356
|
+
appendParallelDeleteExecutionEntry({
|
|
1357
|
+
path: options.executionLogPath,
|
|
1358
|
+
state: options.execution,
|
|
1359
|
+
plan: options.plan,
|
|
1360
|
+
action,
|
|
1361
|
+
context: options.context,
|
|
1362
|
+
status: 'UNKNOWN',
|
|
1363
|
+
recordedAtUtc: options.now(),
|
|
1364
|
+
recovered: true,
|
|
1365
|
+
error: `Read-only recovery failed: ${errorMessage(error)}`,
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
if (rows.length === 0) {
|
|
1371
|
+
const priorCommitted = options.execution.committed.has(action.action_id);
|
|
1372
|
+
if (!priorCommitted) {
|
|
1373
|
+
appendParallelDeleteExecutionEntry({
|
|
1374
|
+
path: options.executionLogPath,
|
|
1375
|
+
state: options.execution,
|
|
1376
|
+
plan: options.plan,
|
|
1377
|
+
action,
|
|
1378
|
+
context: options.context,
|
|
1379
|
+
status: 'COMMITTED',
|
|
1380
|
+
recordedAtUtc: options.now(),
|
|
1381
|
+
recovered: true,
|
|
1382
|
+
readbackSha256: ABSENT_READBACK_SHA256,
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
if (!options.progress.successes.has(action.action_id)) {
|
|
1386
|
+
appendParallelDeleteProgress({
|
|
1387
|
+
progressPath: options.progressPath,
|
|
1388
|
+
progress: options.progress,
|
|
1389
|
+
plan: options.plan,
|
|
1390
|
+
action,
|
|
1391
|
+
context: options.context,
|
|
1392
|
+
startedAtUtc: startedAt,
|
|
1393
|
+
endedAtUtc: options.now(),
|
|
1394
|
+
result: 'success',
|
|
1395
|
+
remoteResultSha256: sha256Json({
|
|
1396
|
+
recovery: 'desired_absent',
|
|
1397
|
+
action_id: action.action_id,
|
|
1398
|
+
desired_sha256: parallelDeleteDesiredSha256(action),
|
|
1399
|
+
}),
|
|
1400
|
+
error: null,
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
statuses.set(action.action_id, 'success');
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
const message = exactParallelDeleteBefore({ rows, action })
|
|
1407
|
+
? 'Prior dispatch has exact-before readback but no zero-dispatch/zero-mutation audit proof; replay is forbidden.'
|
|
1408
|
+
: 'Prior dispatch has ambiguous or drifted readback; replay is forbidden.';
|
|
1409
|
+
if (!options.execution.committed.has(action.action_id) &&
|
|
1410
|
+
options.execution.byAction.get(action.action_id)?.at(-1)?.status !== 'UNKNOWN') {
|
|
1411
|
+
appendParallelDeleteExecutionEntry({
|
|
1412
|
+
path: options.executionLogPath,
|
|
1413
|
+
state: options.execution,
|
|
1414
|
+
plan: options.plan,
|
|
1415
|
+
action,
|
|
1416
|
+
context: options.context,
|
|
1417
|
+
status: 'UNKNOWN',
|
|
1418
|
+
recordedAtUtc: options.now(),
|
|
1419
|
+
recovered: true,
|
|
1420
|
+
readbackSha256: sha256Json(rows.map(snapshotRemoteRow)),
|
|
1421
|
+
error: message,
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
if (!options.progress.successes.has(action.action_id)) {
|
|
1425
|
+
appendParallelDeleteProgress({
|
|
1426
|
+
progressPath: options.progressPath,
|
|
1427
|
+
progress: options.progress,
|
|
1428
|
+
plan: options.plan,
|
|
1429
|
+
action,
|
|
1430
|
+
context: options.context,
|
|
1431
|
+
startedAtUtc: startedAt,
|
|
1432
|
+
endedAtUtc: options.now(),
|
|
1433
|
+
result: 'failed',
|
|
1434
|
+
remoteResultSha256: null,
|
|
1435
|
+
error: `UNKNOWN: ${message}`,
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
statuses.set(action.action_id, 'unknown');
|
|
1439
|
+
};
|
|
1440
|
+
const executeOne = async (action) => {
|
|
1441
|
+
if (options.progress.successes.has(action.action_id)) {
|
|
1442
|
+
statuses.set(action.action_id, 'success');
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
if (options.execution.dispatched.has(action.action_id)) {
|
|
1446
|
+
await recoverAttempted(action);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
const startedAt = options.now();
|
|
1450
|
+
let rows;
|
|
1451
|
+
try {
|
|
1452
|
+
rows = await exactParallelDeleteRows({ context: options.context, action });
|
|
1453
|
+
if (!exactParallelDeleteBefore({ rows, action })) {
|
|
1454
|
+
throw new CliError(`Action row drifted immediately before write: ${action.action_id}`, {
|
|
1455
|
+
code: 'DATASET_MAINTENANCE_ACTION_JUST_IN_TIME_DRIFT',
|
|
1456
|
+
exitCode: 1,
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
catch (error) {
|
|
1461
|
+
appendParallelDeleteProgress({
|
|
1462
|
+
progressPath: options.progressPath,
|
|
1463
|
+
progress: options.progress,
|
|
1464
|
+
plan: options.plan,
|
|
1465
|
+
action,
|
|
1466
|
+
context: options.context,
|
|
1467
|
+
startedAtUtc: startedAt,
|
|
1468
|
+
endedAtUtc: options.now(),
|
|
1469
|
+
result: 'failed',
|
|
1470
|
+
remoteResultSha256: null,
|
|
1471
|
+
error: errorMessage(error),
|
|
1472
|
+
});
|
|
1473
|
+
statuses.set(action.action_id, 'failed');
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
appendParallelDeleteExecutionEntry({
|
|
1477
|
+
path: options.executionLogPath,
|
|
1478
|
+
state: options.execution,
|
|
1479
|
+
plan: options.plan,
|
|
1480
|
+
action,
|
|
1481
|
+
context: options.context,
|
|
1482
|
+
status: 'PREPARED',
|
|
1483
|
+
recordedAtUtc: options.now(),
|
|
1484
|
+
});
|
|
1485
|
+
appendParallelDeleteExecutionEntry({
|
|
1486
|
+
path: options.executionLogPath,
|
|
1487
|
+
state: options.execution,
|
|
1488
|
+
plan: options.plan,
|
|
1489
|
+
action,
|
|
1490
|
+
context: options.context,
|
|
1491
|
+
status: 'DISPATCHED',
|
|
1492
|
+
recordedAtUtc: options.now(),
|
|
1493
|
+
});
|
|
1494
|
+
let remoteResult = null;
|
|
1495
|
+
let dispatchError = null;
|
|
1496
|
+
try {
|
|
1497
|
+
remoteResult = await deleteMaintenanceRow({
|
|
1498
|
+
context: options.context,
|
|
1499
|
+
table: action.table,
|
|
1500
|
+
id: action.id,
|
|
1501
|
+
version: action.version,
|
|
1502
|
+
audit: {
|
|
1503
|
+
plan_sha256: options.plan.plan_sha256,
|
|
1504
|
+
operation_id: options.plan.operation_id,
|
|
1505
|
+
action_id: action.action_id,
|
|
1506
|
+
reason_code: action.reason_code,
|
|
1507
|
+
desired_sha256: parallelDeleteDesiredSha256(action),
|
|
1508
|
+
source: 'tiangong-lca dataset maintenance apply',
|
|
1509
|
+
},
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
catch (error) {
|
|
1513
|
+
dispatchError = error;
|
|
1514
|
+
}
|
|
1515
|
+
let readbackRows = null;
|
|
1516
|
+
let readbackError = null;
|
|
1517
|
+
try {
|
|
1518
|
+
readbackRows = await exactParallelDeleteRows({ context: options.context, action });
|
|
1519
|
+
}
|
|
1520
|
+
catch (error) {
|
|
1521
|
+
readbackError = error;
|
|
1522
|
+
}
|
|
1523
|
+
if (readbackRows?.length === 0) {
|
|
1524
|
+
const remoteResultSha256 = remoteResult ? sha256Json(remoteResult) : null;
|
|
1525
|
+
appendParallelDeleteExecutionEntry({
|
|
1526
|
+
path: options.executionLogPath,
|
|
1527
|
+
state: options.execution,
|
|
1528
|
+
plan: options.plan,
|
|
1529
|
+
action,
|
|
1530
|
+
context: options.context,
|
|
1531
|
+
status: 'COMMITTED',
|
|
1532
|
+
recordedAtUtc: options.now(),
|
|
1533
|
+
recovered: dispatchError !== null,
|
|
1534
|
+
auditId: remoteResult ? remoteAuditId(remoteResult) : null,
|
|
1535
|
+
readbackSha256: ABSENT_READBACK_SHA256,
|
|
1536
|
+
remoteResultSha256,
|
|
1537
|
+
});
|
|
1538
|
+
appendParallelDeleteProgress({
|
|
1539
|
+
progressPath: options.progressPath,
|
|
1540
|
+
progress: options.progress,
|
|
1541
|
+
plan: options.plan,
|
|
1542
|
+
action,
|
|
1543
|
+
context: options.context,
|
|
1544
|
+
startedAtUtc: startedAt,
|
|
1545
|
+
endedAtUtc: options.now(),
|
|
1546
|
+
result: 'success',
|
|
1547
|
+
remoteResultSha256: remoteResultSha256 ??
|
|
1548
|
+
sha256Json({
|
|
1549
|
+
recovery: 'desired_absent',
|
|
1550
|
+
action_id: action.action_id,
|
|
1551
|
+
desired_sha256: parallelDeleteDesiredSha256(action),
|
|
1552
|
+
}),
|
|
1553
|
+
error: null,
|
|
1554
|
+
});
|
|
1555
|
+
statuses.set(action.action_id, 'success');
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
const error = readbackError
|
|
1559
|
+
? `Readback failed after dispatch: ${errorMessage(readbackError)}`
|
|
1560
|
+
: dispatchError
|
|
1561
|
+
? `Dispatch outcome ambiguous and desired absence was not observed: ${errorMessage(dispatchError)}`
|
|
1562
|
+
: 'Delete RPC returned but exact absent readback was not observed.';
|
|
1563
|
+
appendParallelDeleteExecutionEntry({
|
|
1564
|
+
path: options.executionLogPath,
|
|
1565
|
+
state: options.execution,
|
|
1566
|
+
plan: options.plan,
|
|
1567
|
+
action,
|
|
1568
|
+
context: options.context,
|
|
1569
|
+
status: 'UNKNOWN',
|
|
1570
|
+
recordedAtUtc: options.now(),
|
|
1571
|
+
recovered: dispatchError !== null,
|
|
1572
|
+
auditId: remoteResult ? remoteAuditId(remoteResult) : null,
|
|
1573
|
+
readbackSha256: readbackRows ? sha256Json(readbackRows.map(snapshotRemoteRow)) : null,
|
|
1574
|
+
remoteResultSha256: remoteResult ? sha256Json(remoteResult) : null,
|
|
1575
|
+
error,
|
|
1576
|
+
});
|
|
1577
|
+
appendParallelDeleteProgress({
|
|
1578
|
+
progressPath: options.progressPath,
|
|
1579
|
+
progress: options.progress,
|
|
1580
|
+
plan: options.plan,
|
|
1581
|
+
action,
|
|
1582
|
+
context: options.context,
|
|
1583
|
+
startedAtUtc: startedAt,
|
|
1584
|
+
endedAtUtc: options.now(),
|
|
1585
|
+
result: 'failed',
|
|
1586
|
+
remoteResultSha256: null,
|
|
1587
|
+
error: `UNKNOWN: ${error}`,
|
|
1588
|
+
});
|
|
1589
|
+
statuses.set(action.action_id, 'unknown');
|
|
1590
|
+
};
|
|
1591
|
+
let nextIndex = 0;
|
|
1592
|
+
let fatalError = null;
|
|
1593
|
+
const worker = async () => {
|
|
1594
|
+
while (fatalError === null) {
|
|
1595
|
+
const index = nextIndex;
|
|
1596
|
+
nextIndex += 1;
|
|
1597
|
+
if (index >= options.plan.actions.length)
|
|
1598
|
+
return;
|
|
1599
|
+
try {
|
|
1600
|
+
await executeOne(options.plan.actions[index]);
|
|
1601
|
+
}
|
|
1602
|
+
catch (error) {
|
|
1603
|
+
fatalError ??= error;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
await Promise.all(Array.from({ length: Math.min(options.maxParallel, options.plan.actions.length) }, () => worker()));
|
|
1608
|
+
if (fatalError !== null)
|
|
1609
|
+
throw fatalError;
|
|
1610
|
+
return statuses;
|
|
1611
|
+
}
|
|
1029
1612
|
async function executeDerivativeAdmission(options) {
|
|
1030
1613
|
const action = derivativePlanAction(options.plan);
|
|
1031
1614
|
const plannedSnapshot = action.derivative_before;
|
|
@@ -1087,6 +1670,13 @@ function nextAttemptPath(planDir) {
|
|
|
1087
1670
|
}
|
|
1088
1671
|
return path.join(planDir, `commit-report.attempt-${String(attempt).padStart(4, '0')}.json`);
|
|
1089
1672
|
}
|
|
1673
|
+
function nextParallelDeleteInboundBarrierPath(planDir) {
|
|
1674
|
+
let attempt = 1;
|
|
1675
|
+
while (existsSync(path.join(planDir, `parallel-delete-inbound-barrier.attempt-${String(attempt).padStart(4, '0')}.json`))) {
|
|
1676
|
+
attempt += 1;
|
|
1677
|
+
}
|
|
1678
|
+
return path.join(planDir, `parallel-delete-inbound-barrier.attempt-${String(attempt).padStart(4, '0')}.json`);
|
|
1679
|
+
}
|
|
1090
1680
|
export async function runDatasetMaintenanceApply(options) {
|
|
1091
1681
|
if (!options.commit) {
|
|
1092
1682
|
throw new CliError('Dataset maintenance apply requires commit=true.', {
|
|
@@ -1097,6 +1687,11 @@ export async function runDatasetMaintenanceApply(options) {
|
|
|
1097
1687
|
const planPath = path.resolve(options.planPath);
|
|
1098
1688
|
const planDir = path.dirname(planPath);
|
|
1099
1689
|
const plan = parseMaintenancePlan(readJsonFile(planPath, 'Maintenance plan'));
|
|
1690
|
+
const parallelDeleteMode = options.maxParallel !== undefined;
|
|
1691
|
+
const maxParallel = normalizeMaintenanceMaxParallel(options.maxParallel);
|
|
1692
|
+
if (parallelDeleteMode) {
|
|
1693
|
+
assertParallelDeletePlan(plan);
|
|
1694
|
+
}
|
|
1100
1695
|
if (options.approvePlan !== plan.plan_sha256) {
|
|
1101
1696
|
throw new CliError('approvePlan must exactly match the canonical maintenance plan hash.', {
|
|
1102
1697
|
code: 'DATASET_MAINTENANCE_PLAN_APPROVAL_REQUIRED',
|
|
@@ -1153,18 +1748,46 @@ export async function runDatasetMaintenanceApply(options) {
|
|
|
1153
1748
|
const progress = plan.operation === 'rebuild-derivatives'
|
|
1154
1749
|
? { entries: [], successes: new Map(), latestFailures: new Map() }
|
|
1155
1750
|
: parseProgress(plan, progressPath);
|
|
1751
|
+
const executionLogPath = path.join(planDir, 'apply-execution-log.jsonl');
|
|
1752
|
+
const parallelDeleteExecution = parallelDeleteMode
|
|
1753
|
+
? parseParallelDeleteExecutionLog(plan, executionLogPath)
|
|
1754
|
+
: { entries: [], byAction: new Map(), dispatched: new Set(), committed: new Set() };
|
|
1156
1755
|
const resumedSuccesses = progress.successes.size;
|
|
1157
|
-
const current = await
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1756
|
+
const [current, visibleProcesses] = await Promise.all([
|
|
1757
|
+
fetchMaintenanceAccountRows({
|
|
1758
|
+
context,
|
|
1759
|
+
userId: plan.account.user_id,
|
|
1760
|
+
}),
|
|
1761
|
+
parallelDeleteMode
|
|
1762
|
+
? fetchMaintenanceVisibleTableRows({ context, table: 'processes' })
|
|
1763
|
+
: Promise.resolve(null),
|
|
1764
|
+
]);
|
|
1161
1765
|
assertApplyPreconditions({
|
|
1162
1766
|
plan,
|
|
1163
1767
|
planDir,
|
|
1164
1768
|
currentRows: current.rows,
|
|
1165
1769
|
progress,
|
|
1166
1770
|
aliasPlanProgress: { entries: [], success: null, latestFailure: null },
|
|
1771
|
+
attemptedDeleteActionIds: parallelDeleteExecution.dispatched,
|
|
1167
1772
|
});
|
|
1773
|
+
const inboundBarrierPath = nextParallelDeleteInboundBarrierPath(planDir);
|
|
1774
|
+
const inboundBarrier = parallelDeleteMode && visibleProcesses
|
|
1775
|
+
? assertNoVisibleProcessInboundReferences({ plan, rows: visibleProcesses.rows })
|
|
1776
|
+
: null;
|
|
1777
|
+
if (inboundBarrier && visibleProcesses) {
|
|
1778
|
+
writeImmutableJson(inboundBarrierPath, {
|
|
1779
|
+
schema_version: 1,
|
|
1780
|
+
generated_at_utc: clock(options),
|
|
1781
|
+
plan_sha256: plan.plan_sha256,
|
|
1782
|
+
operation_id: plan.operation_id,
|
|
1783
|
+
actor: { user_id: context.account.user_id, email: context.account.email },
|
|
1784
|
+
target_table: 'flows',
|
|
1785
|
+
target_count: plan.actions.length,
|
|
1786
|
+
inbound_reference_count: 0,
|
|
1787
|
+
...inboundBarrier,
|
|
1788
|
+
completeness: visibleProcesses.completeness,
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1168
1791
|
const approvalPath = path.join(planDir, 'approval-record.json');
|
|
1169
1792
|
const approvalAlreadyExisted = existsSync(approvalPath);
|
|
1170
1793
|
validateApprovalRecord({ path: approvalPath, plan, context });
|
|
@@ -1284,6 +1907,80 @@ export async function runDatasetMaintenanceApply(options) {
|
|
|
1284
1907
|
writeJsonArtifact(report.artifacts.commit_report, report);
|
|
1285
1908
|
return report;
|
|
1286
1909
|
}
|
|
1910
|
+
if (parallelDeleteMode) {
|
|
1911
|
+
const executionStatuses = await executeParallelDeletePlan({
|
|
1912
|
+
plan,
|
|
1913
|
+
context,
|
|
1914
|
+
progress,
|
|
1915
|
+
progressPath,
|
|
1916
|
+
executionLogPath,
|
|
1917
|
+
execution: parallelDeleteExecution,
|
|
1918
|
+
maxParallel,
|
|
1919
|
+
now: () => clock(options),
|
|
1920
|
+
});
|
|
1921
|
+
const actions = plan.actions.map((action) => {
|
|
1922
|
+
const status = executionStatuses.get(action.action_id);
|
|
1923
|
+
return {
|
|
1924
|
+
action_id: action.action_id,
|
|
1925
|
+
action: action.action,
|
|
1926
|
+
table: action.table,
|
|
1927
|
+
id: action.id,
|
|
1928
|
+
version: action.version,
|
|
1929
|
+
status,
|
|
1930
|
+
error: progress.latestFailures.get(action.action_id)?.error ?? null,
|
|
1931
|
+
};
|
|
1932
|
+
});
|
|
1933
|
+
const successCount = actions.filter((action) => action.status === 'success').length;
|
|
1934
|
+
const failureCount = actions.filter((action) => action.status === 'failed').length;
|
|
1935
|
+
const unknownCount = actions.filter((action) => action.status === 'unknown').length;
|
|
1936
|
+
const attemptPath = nextAttemptPath(planDir);
|
|
1937
|
+
const report = {
|
|
1938
|
+
schema_version: 1,
|
|
1939
|
+
generated_at_utc: clock(options),
|
|
1940
|
+
status: unknownCount > 0
|
|
1941
|
+
? 'completed_with_unknowns'
|
|
1942
|
+
: successCount === actions.length
|
|
1943
|
+
? 'completed'
|
|
1944
|
+
: 'completed_with_failures',
|
|
1945
|
+
task_id: plan.task_id,
|
|
1946
|
+
operation: plan.operation,
|
|
1947
|
+
operation_id: plan.operation_id,
|
|
1948
|
+
target_mode: plan.target_mode,
|
|
1949
|
+
plan_sha256: plan.plan_sha256,
|
|
1950
|
+
actor: { user_id: context.account.user_id, email: context.account.email },
|
|
1951
|
+
summary: {
|
|
1952
|
+
actions: actions.length,
|
|
1953
|
+
success: successCount,
|
|
1954
|
+
failed: failureCount,
|
|
1955
|
+
unknown: unknownCount,
|
|
1956
|
+
pending: actions.length - successCount - failureCount - unknownCount,
|
|
1957
|
+
resumed_successes: resumedSuccesses,
|
|
1958
|
+
},
|
|
1959
|
+
actions,
|
|
1960
|
+
artifacts: {
|
|
1961
|
+
approval_record: approvalPath,
|
|
1962
|
+
apply_progress: progressPath,
|
|
1963
|
+
execution_log: executionLogPath,
|
|
1964
|
+
inbound_reference_barrier: inboundBarrierPath,
|
|
1965
|
+
commit_report: path.join(planDir, 'commit-report.json'),
|
|
1966
|
+
attempt_report: attemptPath,
|
|
1967
|
+
},
|
|
1968
|
+
database_audit: {
|
|
1969
|
+
rpc_transaction_log: 'public.command_audit_log',
|
|
1970
|
+
source: 'tiangong-lca dataset maintenance apply',
|
|
1971
|
+
correlation_fields: [
|
|
1972
|
+
'plan_sha256',
|
|
1973
|
+
'operation_id',
|
|
1974
|
+
'action_id',
|
|
1975
|
+
'reason_code',
|
|
1976
|
+
'desired_sha256',
|
|
1977
|
+
],
|
|
1978
|
+
},
|
|
1979
|
+
};
|
|
1980
|
+
writeImmutableJson(attemptPath, report);
|
|
1981
|
+
writeJsonArtifact(report.artifacts.commit_report, report);
|
|
1982
|
+
return report;
|
|
1983
|
+
}
|
|
1287
1984
|
const ordered = [...plan.actions].sort((left, right) => {
|
|
1288
1985
|
const rank = {
|
|
1289
1986
|
save_draft: 0,
|
|
@@ -1426,8 +2123,11 @@ export const __testInternals = {
|
|
|
1426
2123
|
aliasExchangeProgressKey,
|
|
1427
2124
|
appendAliasSuccessLogs,
|
|
1428
2125
|
appendAliasProofProgress,
|
|
2126
|
+
appendParallelDeleteExecutionEntry,
|
|
2127
|
+
assertNoVisibleProcessInboundReferences,
|
|
1429
2128
|
assertApplyPreconditions,
|
|
1430
2129
|
assertAliasSupportSnapshots,
|
|
2130
|
+
assertParallelDeletePlan,
|
|
1431
2131
|
buildAliasBatchRequest,
|
|
1432
2132
|
buildAliasPlanRequest,
|
|
1433
2133
|
clock,
|
|
@@ -1435,14 +2135,20 @@ export const __testInternals = {
|
|
|
1435
2135
|
executeDerivativeAdmission,
|
|
1436
2136
|
executeAliasPlan,
|
|
1437
2137
|
executeAction,
|
|
2138
|
+
executeParallelDeletePlan,
|
|
1438
2139
|
finalProjectedRows,
|
|
1439
2140
|
loadDesiredPayload,
|
|
1440
2141
|
nextAttemptPath,
|
|
2142
|
+
nextParallelDeleteInboundBarrierPath,
|
|
2143
|
+
normalizeMaintenanceMaxParallel,
|
|
2144
|
+
parallelDeleteDesiredSha256,
|
|
1441
2145
|
parseDerivativeSubmitProgress,
|
|
1442
2146
|
validateDerivativeAdmissionAttempt,
|
|
1443
2147
|
parseAliasBatchProgress,
|
|
1444
2148
|
parseAliasPlanProgress,
|
|
1445
2149
|
parseProgress,
|
|
2150
|
+
parseParallelDeleteExecutionLog,
|
|
2151
|
+
remoteAuditId,
|
|
1446
2152
|
validateAliasRpcResult,
|
|
1447
2153
|
validateAliasPlanRpcResult,
|
|
1448
2154
|
validateApprovalRecord,
|