@tiangong-lca/cli 0.0.32 → 0.1.0
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/LICENSE +21 -0
- package/README.md +33 -12
- package/assets/import-smoke/simapro.csv +20 -0
- package/bin/tiangong-lca.js +1 -1
- package/dist/src/cli.js +103 -65
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-bilingual.js.map +1 -1
- package/dist/src/lib/dataset-classification.js.map +1 -1
- package/dist/src/lib/dataset-contract.js.map +1 -1
- package/dist/src/lib/dataset-evidence-search.js.map +1 -1
- package/dist/src/lib/dataset-import-lca.js +302 -131
- package/dist/src/lib/dataset-import-lca.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-apply.js +916 -43
- package/dist/src/lib/dataset-maintenance-apply.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-clear-account.js +10 -4
- package/dist/src/lib/dataset-maintenance-clear-account.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-contract.js +1 -6
- package/dist/src/lib/dataset-maintenance-contract.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-derivatives.js +0 -1
- package/dist/src/lib/dataset-maintenance-derivatives.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-flow-identity-capture.js +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-capture.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-pagination.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-protected-before.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-protected-contract.js +0 -1
- package/dist/src/lib/dataset-maintenance-protected-contract.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-protected-freeze.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-protected-preparation.js +0 -2
- package/dist/src/lib/dataset-maintenance-protected-preparation.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-protected-seal.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-protected-verify.js +0 -3
- package/dist/src/lib/dataset-maintenance-protected-verify.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-remote.js +68 -12
- package/dist/src/lib/dataset-maintenance-remote.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-verify.js +1 -2
- package/dist/src/lib/dataset-maintenance-verify.js.map +1 -1
- package/dist/src/lib/dataset-remote-refresh.js.map +1 -1
- package/dist/src/lib/dataset-remote-verify.js +2 -0
- package/dist/src/lib/dataset-remote-verify.js.map +1 -1
- package/dist/src/lib/dataset-save-draft-run.js +2 -0
- package/dist/src/lib/dataset-save-draft-run.js.map +1 -1
- package/dist/src/lib/flow-build-alias-map.js.map +1 -1
- package/dist/src/lib/flow-fetch-rows.js.map +1 -1
- package/dist/src/lib/flow-get.js.map +1 -1
- package/dist/src/lib/flow-governance.js.map +1 -1
- package/dist/src/lib/flow-list.js +1 -0
- package/dist/src/lib/flow-list.js.map +1 -1
- package/dist/src/lib/flow-payload-validation.js.map +1 -1
- package/dist/src/lib/flow-publish-version.js +1 -0
- package/dist/src/lib/flow-publish-version.js.map +1 -1
- package/dist/src/lib/flow-read.js +1 -0
- package/dist/src/lib/flow-read.js.map +1 -1
- package/dist/src/lib/flow-regen-product.js.map +1 -1
- package/dist/src/lib/flow-remediate.js.map +1 -1
- package/dist/src/lib/identity-preflight.js +4 -10
- package/dist/src/lib/identity-preflight.js.map +1 -1
- package/dist/src/lib/lifecyclemodel-bundle-save.js +1 -0
- package/dist/src/lib/lifecyclemodel-bundle-save.js.map +1 -1
- package/dist/src/lib/lifecyclemodel-orchestrate.js.map +1 -1
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js.map +1 -1
- package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -1
- package/dist/src/lib/lifecyclemodel-save-draft-run.js.map +1 -1
- package/dist/src/lib/lifecyclemodel-validate-build.js.map +1 -1
- package/dist/src/lib/package-version.js.map +1 -1
- package/dist/src/lib/process-auto-build.js.map +1 -1
- package/dist/src/lib/process-dedup-review.js +14 -7
- package/dist/src/lib/process-dedup-review.js.map +1 -1
- package/dist/src/lib/process-flow-build-plan.js +111 -30
- package/dist/src/lib/process-flow-build-plan.js.map +1 -1
- package/dist/src/lib/process-get.js +1 -0
- package/dist/src/lib/process-get.js.map +1 -1
- package/dist/src/lib/process-list.js +1 -0
- package/dist/src/lib/process-list.js.map +1 -1
- package/dist/src/lib/process-payload-validation.js.map +1 -1
- package/dist/src/lib/process-qa.js.map +1 -1
- package/dist/src/lib/process-refresh-references.js +15 -10
- package/dist/src/lib/process-refresh-references.js.map +1 -1
- package/dist/src/lib/process-save-draft.js +9 -3
- package/dist/src/lib/process-save-draft.js.map +1 -1
- package/dist/src/lib/process-scope-statistics.js +11 -4
- package/dist/src/lib/process-scope-statistics.js.map +1 -1
- package/dist/src/lib/process-verify-rows.js.map +1 -1
- package/dist/src/lib/publish.js.map +1 -1
- package/dist/src/lib/run.js.map +1 -1
- package/dist/src/lib/supabase-client.js +13 -1
- package/dist/src/lib/supabase-client.js.map +1 -1
- package/dist/src/lib/supabase-data-api-contract.js +328 -0
- package/dist/src/lib/supabase-data-api-contract.js.map +1 -0
- package/dist/src/lib/supabase-data-api-replay.js +69 -0
- package/dist/src/lib/supabase-data-api-replay.js.map +1 -0
- package/dist/src/lib/supabase-json-ordered-write.js +2 -0
- package/dist/src/lib/supabase-json-ordered-write.js.map +1 -1
- package/dist/src/lib/supabase-rest.js +1 -0
- package/dist/src/lib/supabase-rest.js.map +1 -1
- package/dist/src/lib/tidas-sdk-package-validator.js +5 -5
- package/dist/src/lib/tidas-sdk-package-validator.js.map +1 -1
- package/dist/src/lib/validation.js.map +1 -1
- package/dist/src/main.js.map +1 -1
- package/package.json +40 -40
- package/dist/scripts/assert-full-coverage.js +0 -77
- package/dist/scripts/assert-full-coverage.js.map +0 -1
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { closeSync, existsSync, fchmodSync, fsyncSync, openSync, readFileSync, 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, sha256Text, 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 {
|
|
12
|
+
import { applyMaintenanceDerivativeRebuild, deleteMaintenanceRow, fetchMaintenanceAccountRows, fetchMaintenanceDerivativeSnapshot, fetchMaintenanceExactRows, fetchMaintenanceVisibleTableRows, resolveMaintenanceRemoteContext, saveDraftMaintenanceRow, } from './dataset-maintenance-remote.js';
|
|
13
|
+
// Process payloads can be large enough for the default 1,000-row page to exceed
|
|
14
|
+
// the database statement timeout. Keep the destructive all-visible RLS fence,
|
|
15
|
+
// but bound each SELECT-only page so admission can complete without weakening it.
|
|
16
|
+
const PARALLEL_DELETE_VISIBLE_PROCESS_PAGE_SIZE = 250;
|
|
17
|
+
const GLOBAL_INBOUND_PROOF_MAX_AGE_MS = 30 * 60 * 1_000;
|
|
18
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
12
19
|
const POSITIVE_INTEGER_TEXT = /^[1-9]\d*$/u;
|
|
13
20
|
function clock(options) {
|
|
14
21
|
return (options.now ?? new Date()).toISOString();
|
|
@@ -16,6 +23,405 @@ function clock(options) {
|
|
|
16
23
|
function errorMessage(error) {
|
|
17
24
|
return error instanceof Error ? error.message : String(error);
|
|
18
25
|
}
|
|
26
|
+
const ABSENT_READBACK_SHA256 = sha256Json([]);
|
|
27
|
+
function parallelDeleteDesiredSha256(action) {
|
|
28
|
+
return sha256Json({
|
|
29
|
+
action: 'delete',
|
|
30
|
+
table: action.table,
|
|
31
|
+
id: action.id,
|
|
32
|
+
version: action.version,
|
|
33
|
+
desired: 'absent',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function normalizeMaintenanceMaxParallel(value) {
|
|
37
|
+
const normalized = value ?? 1;
|
|
38
|
+
if (!Number.isInteger(normalized) || normalized < 1 || normalized > 8) {
|
|
39
|
+
throw new CliError('--max-parallel must be an integer from 1 to 8.', {
|
|
40
|
+
code: 'DATASET_MAINTENANCE_MAX_PARALLEL_INVALID',
|
|
41
|
+
exitCode: 2,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
function assertParallelDeletePlan(plan) {
|
|
47
|
+
const targets = new Set();
|
|
48
|
+
if (plan.operation !== 'delete' ||
|
|
49
|
+
plan.actions.length === 0 ||
|
|
50
|
+
plan.actions.some((action) => action.action !== 'delete' || action.table !== 'flows') ||
|
|
51
|
+
plan.summary.delete !== plan.actions.length ||
|
|
52
|
+
plan.summary.save_draft !== 0 ||
|
|
53
|
+
(plan.summary.update_json_ordered ?? 0) !== 0 ||
|
|
54
|
+
(plan.summary.rebuild_derivatives ?? 0) !== 0 ||
|
|
55
|
+
plan.summary.current_reference_impacts !== 0 ||
|
|
56
|
+
plan.summary.projected_reference_impacts !== 0) {
|
|
57
|
+
throw new CliError('--max-parallel maintenance apply requires a non-empty flow delete-only plan with zero current and projected inbound references.', {
|
|
58
|
+
code: 'DATASET_MAINTENANCE_PARALLEL_DELETE_PLAN_REQUIRED',
|
|
59
|
+
exitCode: 1,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
for (const action of plan.actions) {
|
|
63
|
+
const target = `${action.table}\u0000${action.id}\u0000${action.version}`;
|
|
64
|
+
if (targets.has(target)) {
|
|
65
|
+
throw new CliError('Parallel delete plan contains a repeated table/id/version target.', {
|
|
66
|
+
code: 'DATASET_MAINTENANCE_PARALLEL_DELETE_TARGET_DUPLICATE',
|
|
67
|
+
exitCode: 1,
|
|
68
|
+
details: { table: action.table, id: action.id, version: action.version },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
targets.add(target);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function parallelDeleteTargetBindingSha256(plan) {
|
|
75
|
+
return sha256Json(plan.actions.map((action, index) => ({
|
|
76
|
+
index,
|
|
77
|
+
ordinal: action.ordinal,
|
|
78
|
+
action_id: action.action_id,
|
|
79
|
+
table: action.table,
|
|
80
|
+
id: action.id,
|
|
81
|
+
version: action.version,
|
|
82
|
+
expected_user_id: action.expected_user_id,
|
|
83
|
+
expected_state_code: action.expected_state_code,
|
|
84
|
+
before_sha256: action.before?.row_sha256 ?? null,
|
|
85
|
+
})));
|
|
86
|
+
}
|
|
87
|
+
function invalidGlobalInboundProof(message, details) {
|
|
88
|
+
throw new CliError(message, {
|
|
89
|
+
code: 'DATASET_MAINTENANCE_GLOBAL_INBOUND_PROOF_INVALID',
|
|
90
|
+
exitCode: 1,
|
|
91
|
+
details,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function assertGlobalInboundProofOptionShape(options) {
|
|
95
|
+
const hasPath = typeof options.proofPath === 'string' && options.proofPath.length > 0;
|
|
96
|
+
const hasApproval = typeof options.approveProof === 'string' && options.approveProof.length > 0;
|
|
97
|
+
if (hasPath !== hasApproval) {
|
|
98
|
+
throw new CliError('--global-inbound-proof and --approve-global-inbound-proof must be provided together.', {
|
|
99
|
+
code: 'DATASET_MAINTENANCE_GLOBAL_INBOUND_PROOF_OPTIONS_INCOMPLETE',
|
|
100
|
+
exitCode: 2,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if ((hasPath || hasApproval) && !options.parallelDeleteMode) {
|
|
104
|
+
throw new CliError('A global inbound proof is allowed only with --max-parallel flow delete-only execution.', {
|
|
105
|
+
code: 'DATASET_MAINTENANCE_GLOBAL_INBOUND_PROOF_MODE_INVALID',
|
|
106
|
+
exitCode: 2,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (hasApproval && !SHA256_PATTERN.test(options.approveProof)) {
|
|
110
|
+
throw new CliError('--approve-global-inbound-proof must be a lowercase SHA-256.', {
|
|
111
|
+
code: 'DATASET_MAINTENANCE_GLOBAL_INBOUND_PROOF_APPROVAL_INVALID',
|
|
112
|
+
exitCode: 2,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function validateParallelDeleteGlobalInboundProof(options) {
|
|
117
|
+
if (!options.proofPath || !options.approveProof)
|
|
118
|
+
return null;
|
|
119
|
+
if (!path.isAbsolute(options.proofPath)) {
|
|
120
|
+
invalidGlobalInboundProof('Global inbound proof path must be absolute.');
|
|
121
|
+
}
|
|
122
|
+
let proofText;
|
|
123
|
+
try {
|
|
124
|
+
proofText = readFileSync(options.proofPath, 'utf8');
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
invalidGlobalInboundProof('Global inbound proof file could not be read.', errorMessage(error));
|
|
128
|
+
}
|
|
129
|
+
const proofSha256 = sha256Text(proofText);
|
|
130
|
+
if (proofSha256 !== options.approveProof) {
|
|
131
|
+
invalidGlobalInboundProof('Global inbound proof file SHA-256 does not match approval.', {
|
|
132
|
+
expected: options.approveProof,
|
|
133
|
+
actual: proofSha256,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
let value;
|
|
137
|
+
try {
|
|
138
|
+
value = JSON.parse(proofText);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
invalidGlobalInboundProof('Global inbound proof is not valid JSON.', errorMessage(error));
|
|
142
|
+
}
|
|
143
|
+
if (!isJsonObject(value)) {
|
|
144
|
+
invalidGlobalInboundProof('Global inbound proof must be a JSON object.');
|
|
145
|
+
}
|
|
146
|
+
const expectedBinding = parallelDeleteTargetBindingSha256(options.plan);
|
|
147
|
+
const capturedAt = typeof value.captured_at_utc === 'string' ? Date.parse(value.captured_at_utc) : NaN;
|
|
148
|
+
const nowMs = options.now.getTime();
|
|
149
|
+
const capturedAgeMs = nowMs - capturedAt;
|
|
150
|
+
const safePositiveInteger = (candidate) => Number.isSafeInteger(candidate) && Number(candidate) > 0;
|
|
151
|
+
if (value.schema_version !== 'dataset-maintenance-global-inbound-proof.v1' ||
|
|
152
|
+
value.status !== 'PASS_GLOBAL_ALL_PROCESS_INBOUND_ZERO' ||
|
|
153
|
+
value.statement_kind !== 'SELECT' ||
|
|
154
|
+
value.source !== 'supabase_select_only_raw_sql' ||
|
|
155
|
+
value.process_scope !== 'all_process_rows_without_rls_restriction' ||
|
|
156
|
+
!Number.isFinite(capturedAt) ||
|
|
157
|
+
capturedAgeMs < -30_000 ||
|
|
158
|
+
capturedAgeMs > GLOBAL_INBOUND_PROOF_MAX_AGE_MS ||
|
|
159
|
+
value.project_ref !== options.context.project_ref ||
|
|
160
|
+
value.actor_user_id !== options.context.account.user_id ||
|
|
161
|
+
value.plan_sha256 !== options.plan.plan_sha256 ||
|
|
162
|
+
value.operation !== 'delete' ||
|
|
163
|
+
value.target_table !== 'flows' ||
|
|
164
|
+
value.target_count !== options.plan.actions.length ||
|
|
165
|
+
value.target_binding_sha256 !== expectedBinding ||
|
|
166
|
+
!safePositiveInteger(value.global_process_rows) ||
|
|
167
|
+
!safePositiveInteger(value.global_exchange_rows) ||
|
|
168
|
+
value.inbound_exchanges !== 0 ||
|
|
169
|
+
value.old_flow_identities_with_inbound !== 0 ||
|
|
170
|
+
value.process_identities_with_inbound !== 0 ||
|
|
171
|
+
value.p0 !== 0 ||
|
|
172
|
+
value.p1 !== 0 ||
|
|
173
|
+
!Array.isArray(value.chunks) ||
|
|
174
|
+
value.chunks.length === 0) {
|
|
175
|
+
invalidGlobalInboundProof('Global inbound proof metadata or zero-inbound result is invalid.', {
|
|
176
|
+
expected_project_ref: options.context.project_ref,
|
|
177
|
+
expected_actor_user_id: options.context.account.user_id,
|
|
178
|
+
expected_plan_sha256: options.plan.plan_sha256,
|
|
179
|
+
expected_target_count: options.plan.actions.length,
|
|
180
|
+
expected_target_binding_sha256: expectedBinding,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
let expectedStart = 0;
|
|
184
|
+
for (const [index, rawChunk] of value.chunks.entries()) {
|
|
185
|
+
if (!isJsonObject(rawChunk)) {
|
|
186
|
+
invalidGlobalInboundProof('Global inbound proof contains a non-object chunk.', { index });
|
|
187
|
+
}
|
|
188
|
+
const chunkCapturedAt = typeof rawChunk.captured_at_utc === 'string' ? Date.parse(rawChunk.captured_at_utc) : NaN;
|
|
189
|
+
const chunkAgeMs = nowMs - chunkCapturedAt;
|
|
190
|
+
if (rawChunk.index !== index ||
|
|
191
|
+
rawChunk.start !== expectedStart ||
|
|
192
|
+
!Number.isSafeInteger(rawChunk.end_exclusive) ||
|
|
193
|
+
Number(rawChunk.end_exclusive) <= expectedStart ||
|
|
194
|
+
Number(rawChunk.end_exclusive) > options.plan.actions.length ||
|
|
195
|
+
rawChunk.target_count !== Number(rawChunk.end_exclusive) - expectedStart ||
|
|
196
|
+
!Number.isFinite(chunkCapturedAt) ||
|
|
197
|
+
chunkAgeMs < -30_000 ||
|
|
198
|
+
chunkAgeMs > GLOBAL_INBOUND_PROOF_MAX_AGE_MS ||
|
|
199
|
+
rawChunk.inbound_exchanges !== 0 ||
|
|
200
|
+
rawChunk.old_flow_identities_with_inbound !== 0 ||
|
|
201
|
+
rawChunk.process_identities_with_inbound !== 0 ||
|
|
202
|
+
rawChunk.owner_draft_inbound !== 0 ||
|
|
203
|
+
rawChunk.public_inbound !== 0 ||
|
|
204
|
+
rawChunk.foreign_private_inbound !== 0 ||
|
|
205
|
+
rawChunk.other_state_inbound !== 0 ||
|
|
206
|
+
typeof rawChunk.sql_sha256 !== 'string' ||
|
|
207
|
+
!SHA256_PATTERN.test(rawChunk.sql_sha256)) {
|
|
208
|
+
invalidGlobalInboundProof('Global inbound proof contains an invalid or incomplete chunk.', {
|
|
209
|
+
index,
|
|
210
|
+
expected_start: expectedStart,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
expectedStart = Number(rawChunk.end_exclusive);
|
|
214
|
+
}
|
|
215
|
+
if (expectedStart !== options.plan.actions.length) {
|
|
216
|
+
invalidGlobalInboundProof('Global inbound proof chunks do not cover every delete target.', {
|
|
217
|
+
expected_end: options.plan.actions.length,
|
|
218
|
+
actual_end: expectedStart,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
...value,
|
|
223
|
+
proof_path: options.proofPath,
|
|
224
|
+
proof_sha256: proofSha256,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function parseParallelDeleteExecutionLog(plan, executionLogPath) {
|
|
228
|
+
const actions = new Map(plan.actions.map((action) => [action.action_id, action]));
|
|
229
|
+
const entries = [];
|
|
230
|
+
const byAction = new Map();
|
|
231
|
+
const dispatched = new Set();
|
|
232
|
+
const committed = new Set();
|
|
233
|
+
for (const value of readJsonLinesIfPresent(executionLogPath)) {
|
|
234
|
+
const action = isJsonObject(value) && typeof value.action_id === 'string'
|
|
235
|
+
? actions.get(value.action_id)
|
|
236
|
+
: null;
|
|
237
|
+
const expectedDesired = action ? parallelDeleteDesiredSha256(action) : null;
|
|
238
|
+
const actionEntries = action ? (byAction.get(action.action_id) ?? []) : [];
|
|
239
|
+
const priorDispatched = actionEntries.some((entry) => entry.status === 'DISPATCHED');
|
|
240
|
+
const priorCommitted = actionEntries.some((entry) => entry.status === 'COMMITTED');
|
|
241
|
+
const status = isJsonObject(value) ? value.status : null;
|
|
242
|
+
const auditIdValid = isJsonObject(value) &&
|
|
243
|
+
(value.audit_id === null ||
|
|
244
|
+
(typeof value.audit_id === 'string' && POSITIVE_INTEGER_TEXT.test(value.audit_id)));
|
|
245
|
+
const remoteHashValid = isJsonObject(value) &&
|
|
246
|
+
(value.remote_result_sha256 === null ||
|
|
247
|
+
(typeof value.remote_result_sha256 === 'string' &&
|
|
248
|
+
/^[a-f0-9]{64}$/u.test(value.remote_result_sha256)));
|
|
249
|
+
const commonOutcomeFieldsValid = isJsonObject(value) &&
|
|
250
|
+
typeof value.attempt_consumed === 'boolean' &&
|
|
251
|
+
typeof value.recovered === 'boolean' &&
|
|
252
|
+
(value.readback_sha256 === null ||
|
|
253
|
+
(typeof value.readback_sha256 === 'string' &&
|
|
254
|
+
/^[a-f0-9]{64}$/u.test(value.readback_sha256))) &&
|
|
255
|
+
(value.error === null || typeof value.error === 'string');
|
|
256
|
+
const statusFieldsValid = isJsonObject(value) &&
|
|
257
|
+
((status === 'PREPARED' &&
|
|
258
|
+
value.attempt_consumed === false &&
|
|
259
|
+
value.recovered === false &&
|
|
260
|
+
value.audit_id === null &&
|
|
261
|
+
value.readback_sha256 === null &&
|
|
262
|
+
value.remote_result_sha256 === null &&
|
|
263
|
+
value.error === null &&
|
|
264
|
+
!priorDispatched &&
|
|
265
|
+
!priorCommitted) ||
|
|
266
|
+
(status === 'DISPATCHED' &&
|
|
267
|
+
value.attempt_consumed === true &&
|
|
268
|
+
value.recovered === false &&
|
|
269
|
+
value.audit_id === null &&
|
|
270
|
+
value.readback_sha256 === null &&
|
|
271
|
+
value.remote_result_sha256 === null &&
|
|
272
|
+
value.error === null &&
|
|
273
|
+
actionEntries.some((entry) => entry.status === 'PREPARED') &&
|
|
274
|
+
!priorDispatched &&
|
|
275
|
+
!priorCommitted) ||
|
|
276
|
+
(status === 'UNKNOWN' &&
|
|
277
|
+
value.attempt_consumed === true &&
|
|
278
|
+
typeof value.error === 'string' &&
|
|
279
|
+
value.error.length > 0 &&
|
|
280
|
+
priorDispatched &&
|
|
281
|
+
!priorCommitted) ||
|
|
282
|
+
(status === 'COMMITTED' &&
|
|
283
|
+
value.attempt_consumed === true &&
|
|
284
|
+
value.readback_sha256 === ABSENT_READBACK_SHA256 &&
|
|
285
|
+
value.error === null &&
|
|
286
|
+
priorDispatched &&
|
|
287
|
+
!priorCommitted));
|
|
288
|
+
if (!isJsonObject(value) ||
|
|
289
|
+
value.schema_version !== 1 ||
|
|
290
|
+
value.plan_sha256 !== plan.plan_sha256 ||
|
|
291
|
+
value.operation_id !== plan.operation_id ||
|
|
292
|
+
!action ||
|
|
293
|
+
action.action !== 'delete' ||
|
|
294
|
+
action.table !== 'flows' ||
|
|
295
|
+
value.attempt_key !== `${action.action_id}@${expectedDesired}` ||
|
|
296
|
+
value.action !== 'delete' ||
|
|
297
|
+
value.table !== 'flows' ||
|
|
298
|
+
value.id !== action.id ||
|
|
299
|
+
value.version !== action.version ||
|
|
300
|
+
value.desired_sha256 !== expectedDesired ||
|
|
301
|
+
value.before_sha256 !== action.before?.row_sha256 ||
|
|
302
|
+
!isJsonObject(value.actor) ||
|
|
303
|
+
value.actor.user_id !== plan.account.user_id ||
|
|
304
|
+
value.actor.email !== plan.account.email ||
|
|
305
|
+
!isJsonObject(value.audit_context) ||
|
|
306
|
+
value.audit_context.plan_sha256 !== plan.plan_sha256 ||
|
|
307
|
+
value.audit_context.operation_id !== plan.operation_id ||
|
|
308
|
+
value.audit_context.action_id !== action.action_id ||
|
|
309
|
+
value.audit_context.reason_code !== action.reason_code ||
|
|
310
|
+
value.audit_context.source !== 'tiangong-lca dataset maintenance apply' ||
|
|
311
|
+
typeof value.recorded_at_utc !== 'string' ||
|
|
312
|
+
!Number.isFinite(Date.parse(value.recorded_at_utc)) ||
|
|
313
|
+
!auditIdValid ||
|
|
314
|
+
!remoteHashValid ||
|
|
315
|
+
!commonOutcomeFieldsValid ||
|
|
316
|
+
!statusFieldsValid) {
|
|
317
|
+
throw new CliError('Parallel delete execution log contains an invalid or foreign entry.', {
|
|
318
|
+
code: 'DATASET_MAINTENANCE_PARALLEL_DELETE_LOG_INVALID',
|
|
319
|
+
exitCode: 1,
|
|
320
|
+
details: value,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
const entry = value;
|
|
324
|
+
entries.push(entry);
|
|
325
|
+
actionEntries.push(entry);
|
|
326
|
+
byAction.set(action.action_id, actionEntries);
|
|
327
|
+
if (entry.status === 'DISPATCHED')
|
|
328
|
+
dispatched.add(action.action_id);
|
|
329
|
+
if (entry.status === 'COMMITTED')
|
|
330
|
+
committed.add(action.action_id);
|
|
331
|
+
}
|
|
332
|
+
return { entries, byAction, dispatched, committed };
|
|
333
|
+
}
|
|
334
|
+
function appendParallelDeleteExecutionEntry(options) {
|
|
335
|
+
const desiredSha256 = parallelDeleteDesiredSha256(options.action);
|
|
336
|
+
const entry = {
|
|
337
|
+
schema_version: 1,
|
|
338
|
+
plan_sha256: options.plan.plan_sha256,
|
|
339
|
+
operation_id: options.plan.operation_id,
|
|
340
|
+
action_id: options.action.action_id,
|
|
341
|
+
attempt_key: `${options.action.action_id}@${desiredSha256}`,
|
|
342
|
+
action: 'delete',
|
|
343
|
+
table: 'flows',
|
|
344
|
+
id: options.action.id,
|
|
345
|
+
version: options.action.version,
|
|
346
|
+
desired_sha256: desiredSha256,
|
|
347
|
+
before_sha256: options.action.before.row_sha256,
|
|
348
|
+
actor: {
|
|
349
|
+
user_id: options.context.account.user_id,
|
|
350
|
+
email: options.context.account.email,
|
|
351
|
+
},
|
|
352
|
+
status: options.status,
|
|
353
|
+
recorded_at_utc: options.recordedAtUtc,
|
|
354
|
+
attempt_consumed: options.status !== 'PREPARED',
|
|
355
|
+
recovered: options.recovered ?? false,
|
|
356
|
+
audit_context: {
|
|
357
|
+
plan_sha256: options.plan.plan_sha256,
|
|
358
|
+
operation_id: options.plan.operation_id,
|
|
359
|
+
action_id: options.action.action_id,
|
|
360
|
+
reason_code: options.action.reason_code,
|
|
361
|
+
source: 'tiangong-lca dataset maintenance apply',
|
|
362
|
+
},
|
|
363
|
+
audit_id: options.auditId ?? null,
|
|
364
|
+
readback_sha256: options.readbackSha256 ?? null,
|
|
365
|
+
remote_result_sha256: options.remoteResultSha256 ?? null,
|
|
366
|
+
error: options.error ?? null,
|
|
367
|
+
};
|
|
368
|
+
const descriptor = openSync(options.path, 'a', 0o600);
|
|
369
|
+
try {
|
|
370
|
+
fchmodSync(descriptor, 0o600);
|
|
371
|
+
writeFileSync(descriptor, `${stableJsonText(entry)}\n`, 'utf8');
|
|
372
|
+
fsyncSync(descriptor);
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
closeSync(descriptor);
|
|
376
|
+
}
|
|
377
|
+
options.state.entries.push(entry);
|
|
378
|
+
const actionEntries = options.state.byAction.get(options.action.action_id) ?? [];
|
|
379
|
+
actionEntries.push(entry);
|
|
380
|
+
options.state.byAction.set(options.action.action_id, actionEntries);
|
|
381
|
+
if (entry.status === 'DISPATCHED')
|
|
382
|
+
options.state.dispatched.add(options.action.action_id);
|
|
383
|
+
if (entry.status === 'COMMITTED')
|
|
384
|
+
options.state.committed.add(options.action.action_id);
|
|
385
|
+
return entry;
|
|
386
|
+
}
|
|
387
|
+
function assertNoVisibleProcessInboundReferences(options) {
|
|
388
|
+
const targets = new Set(options.plan.actions.map((action) => `${action.id}\u0000${action.version}`));
|
|
389
|
+
const targetIds = new Set(options.plan.actions.map((action) => action.id));
|
|
390
|
+
const payloadRows = options.rows
|
|
391
|
+
.filter((row) => row.table === 'processes' && row.json_ordered !== null)
|
|
392
|
+
.map((row) => ({
|
|
393
|
+
table: row.table,
|
|
394
|
+
id: row.id,
|
|
395
|
+
version: row.version,
|
|
396
|
+
json_ordered: row.json_ordered,
|
|
397
|
+
}));
|
|
398
|
+
const references = collectRemoteReferences(payloadRows).filter((reference) => reference.role === 'reference' &&
|
|
399
|
+
reference.table === 'flows' &&
|
|
400
|
+
typeof reference.id === 'string');
|
|
401
|
+
const inbound = references.filter((reference) => reference.version
|
|
402
|
+
? targets.has(`${reference.id}\u0000${reference.version}`)
|
|
403
|
+
: targetIds.has(reference.id));
|
|
404
|
+
if (inbound.length > 0) {
|
|
405
|
+
throw new CliError('Parallel flow delete admission found visible process inbound references.', {
|
|
406
|
+
code: 'DATASET_MAINTENANCE_PARALLEL_DELETE_INBOUND_REFERENCES',
|
|
407
|
+
exitCode: 1,
|
|
408
|
+
details: {
|
|
409
|
+
inbound_count: inbound.length,
|
|
410
|
+
first: inbound.slice(0, 20),
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
process_rows: payloadRows.length,
|
|
416
|
+
process_references: references.length,
|
|
417
|
+
snapshot_sha256: sha256Json(payloadRows.map((row) => ({
|
|
418
|
+
table: row.table,
|
|
419
|
+
id: row.id,
|
|
420
|
+
version: row.version,
|
|
421
|
+
payload_sha256: sha256Json(row.json_ordered),
|
|
422
|
+
}))),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
19
425
|
function loadDesiredPayload(planDir, action) {
|
|
20
426
|
return loadMaintenanceDesiredPayload(planDir, action);
|
|
21
427
|
}
|
|
@@ -413,6 +819,13 @@ function assertApplyPreconditions(options) {
|
|
|
413
819
|
}
|
|
414
820
|
continue;
|
|
415
821
|
}
|
|
822
|
+
if (action.action === 'delete' && options.attemptedDeleteActionIds?.has(action.action_id)) {
|
|
823
|
+
// A request may have committed before its response or progress row was
|
|
824
|
+
// persisted, or the target may have drifted after dispatch. The execution
|
|
825
|
+
// ledger owns both cases and permits only read-only recovery, never an
|
|
826
|
+
// automatic replay.
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
416
829
|
if (!currentRow || currentRow.user_id !== action.expected_user_id) {
|
|
417
830
|
throw new CliError(`Action row is missing, non-draft, or not owned: ${action.action_id}`, {
|
|
418
831
|
code: 'DATASET_MAINTENANCE_ACTION_ROW_DRIFT',
|
|
@@ -627,39 +1040,6 @@ function validateAliasPlanRpcResult(value, plan) {
|
|
|
627
1040
|
raw: value,
|
|
628
1041
|
};
|
|
629
1042
|
}
|
|
630
|
-
async function executeAliasPlan(options) {
|
|
631
|
-
const request = buildAliasPlanRequest(options);
|
|
632
|
-
const remoteResult = await applyMaintenanceAliasPlan({
|
|
633
|
-
context: options.context,
|
|
634
|
-
plan: request,
|
|
635
|
-
});
|
|
636
|
-
const rpc = validateAliasPlanRpcResult(remoteResult, options.plan);
|
|
637
|
-
const afterByAction = new Map();
|
|
638
|
-
for (const action of options.plan.actions) {
|
|
639
|
-
const exact = await fetchMaintenanceExactRows({
|
|
640
|
-
context: options.context,
|
|
641
|
-
table: action.table,
|
|
642
|
-
id: action.id,
|
|
643
|
-
version: action.version,
|
|
644
|
-
});
|
|
645
|
-
const row = exact.rows.length === 1 ? exact.rows[0] : null;
|
|
646
|
-
const snapshot = row ? snapshotRemoteRow(row) : null;
|
|
647
|
-
if (!row ||
|
|
648
|
-
row.user_id !== action.expected_user_id ||
|
|
649
|
-
row.state_code !== 0 ||
|
|
650
|
-
snapshot?.payload_sha256 !== action.desired_payload?.sha256 ||
|
|
651
|
-
row.model_id !== action.before?.model_id ||
|
|
652
|
-
row.rule_verification !== action.before?.rule_verification) {
|
|
653
|
-
throw new CliError(`Alias plan readback failed for action ${action.action_id}.`, {
|
|
654
|
-
code: 'DATASET_MAINTENANCE_ALIAS_READBACK_FAILED',
|
|
655
|
-
exitCode: 1,
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
afterByAction.set(action.action_id, snapshot.row_sha256);
|
|
659
|
-
}
|
|
660
|
-
await assertAliasSupportSnapshots({ plan: options.plan, context: options.context });
|
|
661
|
-
return { rpc, after_by_action: afterByAction };
|
|
662
|
-
}
|
|
663
1043
|
function aliasExchangeProgressKey(value) {
|
|
664
1044
|
return `${value.batch_id}\u0000${value.action_id}\u0000${value.exchange_index}\u0000${value.data_set_internal_id}`;
|
|
665
1045
|
}
|
|
@@ -1026,6 +1406,335 @@ async function executeAction(options) {
|
|
|
1026
1406
|
}
|
|
1027
1407
|
return { afterSha256: null, remoteResultSha256: sha256Json(remoteResult) };
|
|
1028
1408
|
}
|
|
1409
|
+
function appendParallelDeleteProgress(options) {
|
|
1410
|
+
const entry = {
|
|
1411
|
+
schema_version: 1,
|
|
1412
|
+
plan_sha256: options.plan.plan_sha256,
|
|
1413
|
+
operation_id: options.plan.operation_id,
|
|
1414
|
+
action_id: options.action.action_id,
|
|
1415
|
+
action: 'delete',
|
|
1416
|
+
table: options.action.table,
|
|
1417
|
+
id: options.action.id,
|
|
1418
|
+
version: options.action.version,
|
|
1419
|
+
reason_code: options.action.reason_code,
|
|
1420
|
+
audit_context: {
|
|
1421
|
+
plan_sha256: options.plan.plan_sha256,
|
|
1422
|
+
operation_id: options.plan.operation_id,
|
|
1423
|
+
action_id: options.action.action_id,
|
|
1424
|
+
reason_code: options.action.reason_code,
|
|
1425
|
+
source: 'tiangong-lca dataset maintenance apply',
|
|
1426
|
+
},
|
|
1427
|
+
actor: {
|
|
1428
|
+
user_id: options.context.account.user_id,
|
|
1429
|
+
email: options.context.account.email,
|
|
1430
|
+
},
|
|
1431
|
+
started_at_utc: options.startedAtUtc,
|
|
1432
|
+
ended_at_utc: options.endedAtUtc,
|
|
1433
|
+
before_sha256: options.action.before.row_sha256,
|
|
1434
|
+
after_sha256: null,
|
|
1435
|
+
remote_result_sha256: options.remoteResultSha256,
|
|
1436
|
+
result: options.result,
|
|
1437
|
+
error: options.error,
|
|
1438
|
+
rollback: options.action.rollback,
|
|
1439
|
+
};
|
|
1440
|
+
appendStableJsonLine(options.progressPath, entry);
|
|
1441
|
+
options.progress.entries.push(entry);
|
|
1442
|
+
if (entry.result === 'success') {
|
|
1443
|
+
options.progress.successes.set(entry.action_id, entry);
|
|
1444
|
+
options.progress.latestFailures.delete(entry.action_id);
|
|
1445
|
+
}
|
|
1446
|
+
else if (!options.progress.successes.has(entry.action_id)) {
|
|
1447
|
+
options.progress.latestFailures.set(entry.action_id, entry);
|
|
1448
|
+
}
|
|
1449
|
+
return entry;
|
|
1450
|
+
}
|
|
1451
|
+
function remoteAuditId(value) {
|
|
1452
|
+
const candidate = value.audit_id;
|
|
1453
|
+
return typeof candidate === 'string' && POSITIVE_INTEGER_TEXT.test(candidate) ? candidate : null;
|
|
1454
|
+
}
|
|
1455
|
+
async function exactParallelDeleteRows(options) {
|
|
1456
|
+
return (await fetchMaintenanceExactRows({
|
|
1457
|
+
context: options.context,
|
|
1458
|
+
table: options.action.table,
|
|
1459
|
+
id: options.action.id,
|
|
1460
|
+
version: options.action.version,
|
|
1461
|
+
})).rows;
|
|
1462
|
+
}
|
|
1463
|
+
function exactParallelDeleteBefore(options) {
|
|
1464
|
+
const row = options.rows[0];
|
|
1465
|
+
return Boolean(options.rows.length === 1 &&
|
|
1466
|
+
row &&
|
|
1467
|
+
row.user_id === options.action.expected_user_id &&
|
|
1468
|
+
row.state_code === 0 &&
|
|
1469
|
+
snapshotRemoteRow(row).row_sha256 === options.action.before?.row_sha256);
|
|
1470
|
+
}
|
|
1471
|
+
async function executeParallelDeletePlan(options) {
|
|
1472
|
+
const statuses = new Map();
|
|
1473
|
+
const recoverAttempted = async (action) => {
|
|
1474
|
+
const startedAt = options.now();
|
|
1475
|
+
let rows;
|
|
1476
|
+
try {
|
|
1477
|
+
rows = await exactParallelDeleteRows({ context: options.context, action });
|
|
1478
|
+
}
|
|
1479
|
+
catch (error) {
|
|
1480
|
+
statuses.set(action.action_id, 'unknown');
|
|
1481
|
+
if (options.execution.byAction.get(action.action_id)?.at(-1)?.status !== 'UNKNOWN') {
|
|
1482
|
+
appendParallelDeleteExecutionEntry({
|
|
1483
|
+
path: options.executionLogPath,
|
|
1484
|
+
state: options.execution,
|
|
1485
|
+
plan: options.plan,
|
|
1486
|
+
action,
|
|
1487
|
+
context: options.context,
|
|
1488
|
+
status: 'UNKNOWN',
|
|
1489
|
+
recordedAtUtc: options.now(),
|
|
1490
|
+
recovered: true,
|
|
1491
|
+
error: `Read-only recovery failed: ${errorMessage(error)}`,
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (rows.length === 0) {
|
|
1497
|
+
const priorCommitted = options.execution.committed.has(action.action_id);
|
|
1498
|
+
if (!priorCommitted) {
|
|
1499
|
+
appendParallelDeleteExecutionEntry({
|
|
1500
|
+
path: options.executionLogPath,
|
|
1501
|
+
state: options.execution,
|
|
1502
|
+
plan: options.plan,
|
|
1503
|
+
action,
|
|
1504
|
+
context: options.context,
|
|
1505
|
+
status: 'COMMITTED',
|
|
1506
|
+
recordedAtUtc: options.now(),
|
|
1507
|
+
recovered: true,
|
|
1508
|
+
readbackSha256: ABSENT_READBACK_SHA256,
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
if (!options.progress.successes.has(action.action_id)) {
|
|
1512
|
+
appendParallelDeleteProgress({
|
|
1513
|
+
progressPath: options.progressPath,
|
|
1514
|
+
progress: options.progress,
|
|
1515
|
+
plan: options.plan,
|
|
1516
|
+
action,
|
|
1517
|
+
context: options.context,
|
|
1518
|
+
startedAtUtc: startedAt,
|
|
1519
|
+
endedAtUtc: options.now(),
|
|
1520
|
+
result: 'success',
|
|
1521
|
+
remoteResultSha256: sha256Json({
|
|
1522
|
+
recovery: 'desired_absent',
|
|
1523
|
+
action_id: action.action_id,
|
|
1524
|
+
desired_sha256: parallelDeleteDesiredSha256(action),
|
|
1525
|
+
}),
|
|
1526
|
+
error: null,
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
statuses.set(action.action_id, 'success');
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const message = exactParallelDeleteBefore({ rows, action })
|
|
1533
|
+
? 'Prior dispatch has exact-before readback but no zero-dispatch/zero-mutation audit proof; replay is forbidden.'
|
|
1534
|
+
: 'Prior dispatch has ambiguous or drifted readback; replay is forbidden.';
|
|
1535
|
+
if (!options.execution.committed.has(action.action_id) &&
|
|
1536
|
+
options.execution.byAction.get(action.action_id)?.at(-1)?.status !== 'UNKNOWN') {
|
|
1537
|
+
appendParallelDeleteExecutionEntry({
|
|
1538
|
+
path: options.executionLogPath,
|
|
1539
|
+
state: options.execution,
|
|
1540
|
+
plan: options.plan,
|
|
1541
|
+
action,
|
|
1542
|
+
context: options.context,
|
|
1543
|
+
status: 'UNKNOWN',
|
|
1544
|
+
recordedAtUtc: options.now(),
|
|
1545
|
+
recovered: true,
|
|
1546
|
+
readbackSha256: sha256Json(rows.map(snapshotRemoteRow)),
|
|
1547
|
+
error: message,
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
if (!options.progress.successes.has(action.action_id)) {
|
|
1551
|
+
appendParallelDeleteProgress({
|
|
1552
|
+
progressPath: options.progressPath,
|
|
1553
|
+
progress: options.progress,
|
|
1554
|
+
plan: options.plan,
|
|
1555
|
+
action,
|
|
1556
|
+
context: options.context,
|
|
1557
|
+
startedAtUtc: startedAt,
|
|
1558
|
+
endedAtUtc: options.now(),
|
|
1559
|
+
result: 'failed',
|
|
1560
|
+
remoteResultSha256: null,
|
|
1561
|
+
error: `UNKNOWN: ${message}`,
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
statuses.set(action.action_id, 'unknown');
|
|
1565
|
+
};
|
|
1566
|
+
const executeOne = async (action) => {
|
|
1567
|
+
if (options.progress.successes.has(action.action_id)) {
|
|
1568
|
+
statuses.set(action.action_id, 'success');
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
if (options.execution.dispatched.has(action.action_id)) {
|
|
1572
|
+
await recoverAttempted(action);
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
const startedAt = options.now();
|
|
1576
|
+
let rows;
|
|
1577
|
+
try {
|
|
1578
|
+
rows = await exactParallelDeleteRows({ context: options.context, action });
|
|
1579
|
+
if (!exactParallelDeleteBefore({ rows, action })) {
|
|
1580
|
+
throw new CliError(`Action row drifted immediately before write: ${action.action_id}`, {
|
|
1581
|
+
code: 'DATASET_MAINTENANCE_ACTION_JUST_IN_TIME_DRIFT',
|
|
1582
|
+
exitCode: 1,
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
catch (error) {
|
|
1587
|
+
appendParallelDeleteProgress({
|
|
1588
|
+
progressPath: options.progressPath,
|
|
1589
|
+
progress: options.progress,
|
|
1590
|
+
plan: options.plan,
|
|
1591
|
+
action,
|
|
1592
|
+
context: options.context,
|
|
1593
|
+
startedAtUtc: startedAt,
|
|
1594
|
+
endedAtUtc: options.now(),
|
|
1595
|
+
result: 'failed',
|
|
1596
|
+
remoteResultSha256: null,
|
|
1597
|
+
error: errorMessage(error),
|
|
1598
|
+
});
|
|
1599
|
+
statuses.set(action.action_id, 'failed');
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
appendParallelDeleteExecutionEntry({
|
|
1603
|
+
path: options.executionLogPath,
|
|
1604
|
+
state: options.execution,
|
|
1605
|
+
plan: options.plan,
|
|
1606
|
+
action,
|
|
1607
|
+
context: options.context,
|
|
1608
|
+
status: 'PREPARED',
|
|
1609
|
+
recordedAtUtc: options.now(),
|
|
1610
|
+
});
|
|
1611
|
+
appendParallelDeleteExecutionEntry({
|
|
1612
|
+
path: options.executionLogPath,
|
|
1613
|
+
state: options.execution,
|
|
1614
|
+
plan: options.plan,
|
|
1615
|
+
action,
|
|
1616
|
+
context: options.context,
|
|
1617
|
+
status: 'DISPATCHED',
|
|
1618
|
+
recordedAtUtc: options.now(),
|
|
1619
|
+
});
|
|
1620
|
+
let remoteResult = null;
|
|
1621
|
+
let dispatchError = null;
|
|
1622
|
+
try {
|
|
1623
|
+
remoteResult = await deleteMaintenanceRow({
|
|
1624
|
+
context: options.context,
|
|
1625
|
+
table: action.table,
|
|
1626
|
+
id: action.id,
|
|
1627
|
+
version: action.version,
|
|
1628
|
+
audit: {
|
|
1629
|
+
plan_sha256: options.plan.plan_sha256,
|
|
1630
|
+
operation_id: options.plan.operation_id,
|
|
1631
|
+
action_id: action.action_id,
|
|
1632
|
+
reason_code: action.reason_code,
|
|
1633
|
+
desired_sha256: parallelDeleteDesiredSha256(action),
|
|
1634
|
+
source: 'tiangong-lca dataset maintenance apply',
|
|
1635
|
+
},
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
catch (error) {
|
|
1639
|
+
dispatchError = error;
|
|
1640
|
+
}
|
|
1641
|
+
let readbackRows = null;
|
|
1642
|
+
let readbackError = null;
|
|
1643
|
+
try {
|
|
1644
|
+
readbackRows = await exactParallelDeleteRows({ context: options.context, action });
|
|
1645
|
+
}
|
|
1646
|
+
catch (error) {
|
|
1647
|
+
readbackError = error;
|
|
1648
|
+
}
|
|
1649
|
+
if (readbackRows?.length === 0) {
|
|
1650
|
+
const remoteResultSha256 = remoteResult ? sha256Json(remoteResult) : null;
|
|
1651
|
+
appendParallelDeleteExecutionEntry({
|
|
1652
|
+
path: options.executionLogPath,
|
|
1653
|
+
state: options.execution,
|
|
1654
|
+
plan: options.plan,
|
|
1655
|
+
action,
|
|
1656
|
+
context: options.context,
|
|
1657
|
+
status: 'COMMITTED',
|
|
1658
|
+
recordedAtUtc: options.now(),
|
|
1659
|
+
recovered: dispatchError !== null,
|
|
1660
|
+
auditId: remoteResult ? remoteAuditId(remoteResult) : null,
|
|
1661
|
+
readbackSha256: ABSENT_READBACK_SHA256,
|
|
1662
|
+
remoteResultSha256,
|
|
1663
|
+
});
|
|
1664
|
+
appendParallelDeleteProgress({
|
|
1665
|
+
progressPath: options.progressPath,
|
|
1666
|
+
progress: options.progress,
|
|
1667
|
+
plan: options.plan,
|
|
1668
|
+
action,
|
|
1669
|
+
context: options.context,
|
|
1670
|
+
startedAtUtc: startedAt,
|
|
1671
|
+
endedAtUtc: options.now(),
|
|
1672
|
+
result: 'success',
|
|
1673
|
+
remoteResultSha256: remoteResultSha256 ??
|
|
1674
|
+
sha256Json({
|
|
1675
|
+
recovery: 'desired_absent',
|
|
1676
|
+
action_id: action.action_id,
|
|
1677
|
+
desired_sha256: parallelDeleteDesiredSha256(action),
|
|
1678
|
+
}),
|
|
1679
|
+
error: null,
|
|
1680
|
+
});
|
|
1681
|
+
statuses.set(action.action_id, 'success');
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const error = readbackError
|
|
1685
|
+
? `Readback failed after dispatch: ${errorMessage(readbackError)}`
|
|
1686
|
+
: dispatchError
|
|
1687
|
+
? `Dispatch outcome ambiguous and desired absence was not observed: ${errorMessage(dispatchError)}`
|
|
1688
|
+
: 'Delete RPC returned but exact absent readback was not observed.';
|
|
1689
|
+
appendParallelDeleteExecutionEntry({
|
|
1690
|
+
path: options.executionLogPath,
|
|
1691
|
+
state: options.execution,
|
|
1692
|
+
plan: options.plan,
|
|
1693
|
+
action,
|
|
1694
|
+
context: options.context,
|
|
1695
|
+
status: 'UNKNOWN',
|
|
1696
|
+
recordedAtUtc: options.now(),
|
|
1697
|
+
recovered: dispatchError !== null,
|
|
1698
|
+
auditId: remoteResult ? remoteAuditId(remoteResult) : null,
|
|
1699
|
+
readbackSha256: readbackRows ? sha256Json(readbackRows.map(snapshotRemoteRow)) : null,
|
|
1700
|
+
remoteResultSha256: remoteResult ? sha256Json(remoteResult) : null,
|
|
1701
|
+
error,
|
|
1702
|
+
});
|
|
1703
|
+
appendParallelDeleteProgress({
|
|
1704
|
+
progressPath: options.progressPath,
|
|
1705
|
+
progress: options.progress,
|
|
1706
|
+
plan: options.plan,
|
|
1707
|
+
action,
|
|
1708
|
+
context: options.context,
|
|
1709
|
+
startedAtUtc: startedAt,
|
|
1710
|
+
endedAtUtc: options.now(),
|
|
1711
|
+
result: 'failed',
|
|
1712
|
+
remoteResultSha256: null,
|
|
1713
|
+
error: `UNKNOWN: ${error}`,
|
|
1714
|
+
});
|
|
1715
|
+
statuses.set(action.action_id, 'unknown');
|
|
1716
|
+
};
|
|
1717
|
+
let nextIndex = 0;
|
|
1718
|
+
let fatalError = null;
|
|
1719
|
+
const worker = async () => {
|
|
1720
|
+
while (fatalError === null) {
|
|
1721
|
+
const index = nextIndex;
|
|
1722
|
+
nextIndex += 1;
|
|
1723
|
+
if (index >= options.plan.actions.length)
|
|
1724
|
+
return;
|
|
1725
|
+
try {
|
|
1726
|
+
await executeOne(options.plan.actions[index]);
|
|
1727
|
+
}
|
|
1728
|
+
catch (error) {
|
|
1729
|
+
fatalError ??= error;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
await Promise.all(Array.from({ length: Math.min(options.maxParallel, options.plan.actions.length) }, () => worker()));
|
|
1734
|
+
if (fatalError !== null)
|
|
1735
|
+
throw fatalError;
|
|
1736
|
+
return statuses;
|
|
1737
|
+
}
|
|
1029
1738
|
async function executeDerivativeAdmission(options) {
|
|
1030
1739
|
const action = derivativePlanAction(options.plan);
|
|
1031
1740
|
const plannedSnapshot = action.derivative_before;
|
|
@@ -1036,8 +1745,7 @@ async function executeDerivativeAdmission(options) {
|
|
|
1036
1745
|
}), { id: action.id, version: action.version, userId: action.expected_user_id });
|
|
1037
1746
|
if (preflight.modified_at !== plannedSnapshot.modified_at ||
|
|
1038
1747
|
preflight.json_sha256 !== plannedSnapshot.json_sha256 ||
|
|
1039
|
-
preflight.json_ordered_sha256 !== plannedSnapshot.json_ordered_sha256
|
|
1040
|
-
preflight.extracted_text_sha256 !== plannedSnapshot.extracted_text_sha256) {
|
|
1748
|
+
preflight.json_ordered_sha256 !== plannedSnapshot.json_ordered_sha256) {
|
|
1041
1749
|
throw new CliError('Derivative action primary preconditions drifted after planning.', {
|
|
1042
1750
|
code: 'DATASET_MAINTENANCE_DERIVATIVE_PRIMARY_DRIFT',
|
|
1043
1751
|
exitCode: 1,
|
|
@@ -1087,6 +1795,13 @@ function nextAttemptPath(planDir) {
|
|
|
1087
1795
|
}
|
|
1088
1796
|
return path.join(planDir, `commit-report.attempt-${String(attempt).padStart(4, '0')}.json`);
|
|
1089
1797
|
}
|
|
1798
|
+
function nextParallelDeleteInboundBarrierPath(planDir) {
|
|
1799
|
+
let attempt = 1;
|
|
1800
|
+
while (existsSync(path.join(planDir, `parallel-delete-inbound-barrier.attempt-${String(attempt).padStart(4, '0')}.json`))) {
|
|
1801
|
+
attempt += 1;
|
|
1802
|
+
}
|
|
1803
|
+
return path.join(planDir, `parallel-delete-inbound-barrier.attempt-${String(attempt).padStart(4, '0')}.json`);
|
|
1804
|
+
}
|
|
1090
1805
|
export async function runDatasetMaintenanceApply(options) {
|
|
1091
1806
|
if (!options.commit) {
|
|
1092
1807
|
throw new CliError('Dataset maintenance apply requires commit=true.', {
|
|
@@ -1097,6 +1812,16 @@ export async function runDatasetMaintenanceApply(options) {
|
|
|
1097
1812
|
const planPath = path.resolve(options.planPath);
|
|
1098
1813
|
const planDir = path.dirname(planPath);
|
|
1099
1814
|
const plan = parseMaintenancePlan(readJsonFile(planPath, 'Maintenance plan'));
|
|
1815
|
+
const parallelDeleteMode = options.maxParallel !== undefined;
|
|
1816
|
+
const maxParallel = normalizeMaintenanceMaxParallel(options.maxParallel);
|
|
1817
|
+
assertGlobalInboundProofOptionShape({
|
|
1818
|
+
parallelDeleteMode,
|
|
1819
|
+
proofPath: options.globalInboundProofPath,
|
|
1820
|
+
approveProof: options.approveGlobalInboundProof,
|
|
1821
|
+
});
|
|
1822
|
+
if (parallelDeleteMode) {
|
|
1823
|
+
assertParallelDeletePlan(plan);
|
|
1824
|
+
}
|
|
1100
1825
|
if (options.approvePlan !== plan.plan_sha256) {
|
|
1101
1826
|
throw new CliError('approvePlan must exactly match the canonical maintenance plan hash.', {
|
|
1102
1827
|
code: 'DATASET_MAINTENANCE_PLAN_APPROVAL_REQUIRED',
|
|
@@ -1147,24 +1872,87 @@ export async function runDatasetMaintenanceApply(options) {
|
|
|
1147
1872
|
exitCode: 2,
|
|
1148
1873
|
});
|
|
1149
1874
|
}
|
|
1875
|
+
const globalInboundProof = parallelDeleteMode
|
|
1876
|
+
? validateParallelDeleteGlobalInboundProof({
|
|
1877
|
+
proofPath: options.globalInboundProofPath,
|
|
1878
|
+
approveProof: options.approveGlobalInboundProof,
|
|
1879
|
+
plan,
|
|
1880
|
+
context,
|
|
1881
|
+
now: options.now ?? new Date(),
|
|
1882
|
+
})
|
|
1883
|
+
: null;
|
|
1150
1884
|
const derivativeProgress = plan.operation === 'rebuild-derivatives'
|
|
1151
1885
|
? parseDerivativeSubmitProgress(plan, progressPath)
|
|
1152
1886
|
: { entries: [], latest: null };
|
|
1153
1887
|
const progress = plan.operation === 'rebuild-derivatives'
|
|
1154
1888
|
? { entries: [], successes: new Map(), latestFailures: new Map() }
|
|
1155
1889
|
: parseProgress(plan, progressPath);
|
|
1890
|
+
const executionLogPath = path.join(planDir, 'apply-execution-log.jsonl');
|
|
1891
|
+
const parallelDeleteExecution = parallelDeleteMode
|
|
1892
|
+
? parseParallelDeleteExecutionLog(plan, executionLogPath)
|
|
1893
|
+
: { entries: [], byAction: new Map(), dispatched: new Set(), committed: new Set() };
|
|
1156
1894
|
const resumedSuccesses = progress.successes.size;
|
|
1157
|
-
const current = await
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1895
|
+
const [current, visibleProcesses] = await Promise.all([
|
|
1896
|
+
fetchMaintenanceAccountRows({
|
|
1897
|
+
context,
|
|
1898
|
+
userId: plan.account.user_id,
|
|
1899
|
+
}),
|
|
1900
|
+
parallelDeleteMode && !globalInboundProof
|
|
1901
|
+
? fetchMaintenanceVisibleTableRows({
|
|
1902
|
+
context,
|
|
1903
|
+
table: 'processes',
|
|
1904
|
+
pageSize: PARALLEL_DELETE_VISIBLE_PROCESS_PAGE_SIZE,
|
|
1905
|
+
})
|
|
1906
|
+
: Promise.resolve(null),
|
|
1907
|
+
]);
|
|
1161
1908
|
assertApplyPreconditions({
|
|
1162
1909
|
plan,
|
|
1163
1910
|
planDir,
|
|
1164
1911
|
currentRows: current.rows,
|
|
1165
1912
|
progress,
|
|
1166
1913
|
aliasPlanProgress: { entries: [], success: null, latestFailure: null },
|
|
1914
|
+
attemptedDeleteActionIds: parallelDeleteExecution.dispatched,
|
|
1167
1915
|
});
|
|
1916
|
+
const inboundBarrierPath = nextParallelDeleteInboundBarrierPath(planDir);
|
|
1917
|
+
const inboundBarrier = globalInboundProof
|
|
1918
|
+
? {
|
|
1919
|
+
process_rows: globalInboundProof.global_process_rows,
|
|
1920
|
+
process_references: globalInboundProof.global_exchange_rows,
|
|
1921
|
+
snapshot_sha256: globalInboundProof.proof_sha256,
|
|
1922
|
+
}
|
|
1923
|
+
: parallelDeleteMode && visibleProcesses
|
|
1924
|
+
? assertNoVisibleProcessInboundReferences({ plan, rows: visibleProcesses.rows })
|
|
1925
|
+
: null;
|
|
1926
|
+
if (inboundBarrier && (visibleProcesses || globalInboundProof)) {
|
|
1927
|
+
writeImmutableJson(inboundBarrierPath, {
|
|
1928
|
+
schema_version: 1,
|
|
1929
|
+
generated_at_utc: clock(options),
|
|
1930
|
+
plan_sha256: plan.plan_sha256,
|
|
1931
|
+
operation_id: plan.operation_id,
|
|
1932
|
+
actor: { user_id: context.account.user_id, email: context.account.email },
|
|
1933
|
+
target_table: 'flows',
|
|
1934
|
+
target_count: plan.actions.length,
|
|
1935
|
+
inbound_reference_count: 0,
|
|
1936
|
+
...inboundBarrier,
|
|
1937
|
+
completeness: globalInboundProof
|
|
1938
|
+
? {
|
|
1939
|
+
status: 'complete',
|
|
1940
|
+
complete: true,
|
|
1941
|
+
strategy: 'sha256_approved_global_select_only_all_processes',
|
|
1942
|
+
statement_kind: globalInboundProof.statement_kind,
|
|
1943
|
+
process_scope: globalInboundProof.process_scope,
|
|
1944
|
+
proof_path: globalInboundProof.proof_path,
|
|
1945
|
+
proof_sha256: globalInboundProof.proof_sha256,
|
|
1946
|
+
captured_at_utc: globalInboundProof.captured_at_utc,
|
|
1947
|
+
target_binding_sha256: globalInboundProof.target_binding_sha256,
|
|
1948
|
+
chunk_count: globalInboundProof.chunks.length,
|
|
1949
|
+
global_process_rows: globalInboundProof.global_process_rows,
|
|
1950
|
+
global_exchange_rows: globalInboundProof.global_exchange_rows,
|
|
1951
|
+
inbound_exchanges: globalInboundProof.inbound_exchanges,
|
|
1952
|
+
}
|
|
1953
|
+
: visibleProcesses.completeness,
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1168
1956
|
const approvalPath = path.join(planDir, 'approval-record.json');
|
|
1169
1957
|
const approvalAlreadyExisted = existsSync(approvalPath);
|
|
1170
1958
|
validateApprovalRecord({ path: approvalPath, plan, context });
|
|
@@ -1284,6 +2072,80 @@ export async function runDatasetMaintenanceApply(options) {
|
|
|
1284
2072
|
writeJsonArtifact(report.artifacts.commit_report, report);
|
|
1285
2073
|
return report;
|
|
1286
2074
|
}
|
|
2075
|
+
if (parallelDeleteMode) {
|
|
2076
|
+
const executionStatuses = await executeParallelDeletePlan({
|
|
2077
|
+
plan,
|
|
2078
|
+
context,
|
|
2079
|
+
progress,
|
|
2080
|
+
progressPath,
|
|
2081
|
+
executionLogPath,
|
|
2082
|
+
execution: parallelDeleteExecution,
|
|
2083
|
+
maxParallel,
|
|
2084
|
+
now: () => clock(options),
|
|
2085
|
+
});
|
|
2086
|
+
const actions = plan.actions.map((action) => {
|
|
2087
|
+
const status = executionStatuses.get(action.action_id);
|
|
2088
|
+
return {
|
|
2089
|
+
action_id: action.action_id,
|
|
2090
|
+
action: action.action,
|
|
2091
|
+
table: action.table,
|
|
2092
|
+
id: action.id,
|
|
2093
|
+
version: action.version,
|
|
2094
|
+
status,
|
|
2095
|
+
error: progress.latestFailures.get(action.action_id)?.error ?? null,
|
|
2096
|
+
};
|
|
2097
|
+
});
|
|
2098
|
+
const successCount = actions.filter((action) => action.status === 'success').length;
|
|
2099
|
+
const failureCount = actions.filter((action) => action.status === 'failed').length;
|
|
2100
|
+
const unknownCount = actions.filter((action) => action.status === 'unknown').length;
|
|
2101
|
+
const attemptPath = nextAttemptPath(planDir);
|
|
2102
|
+
const report = {
|
|
2103
|
+
schema_version: 1,
|
|
2104
|
+
generated_at_utc: clock(options),
|
|
2105
|
+
status: unknownCount > 0
|
|
2106
|
+
? 'completed_with_unknowns'
|
|
2107
|
+
: successCount === actions.length
|
|
2108
|
+
? 'completed'
|
|
2109
|
+
: 'completed_with_failures',
|
|
2110
|
+
task_id: plan.task_id,
|
|
2111
|
+
operation: plan.operation,
|
|
2112
|
+
operation_id: plan.operation_id,
|
|
2113
|
+
target_mode: plan.target_mode,
|
|
2114
|
+
plan_sha256: plan.plan_sha256,
|
|
2115
|
+
actor: { user_id: context.account.user_id, email: context.account.email },
|
|
2116
|
+
summary: {
|
|
2117
|
+
actions: actions.length,
|
|
2118
|
+
success: successCount,
|
|
2119
|
+
failed: failureCount,
|
|
2120
|
+
unknown: unknownCount,
|
|
2121
|
+
pending: actions.length - successCount - failureCount - unknownCount,
|
|
2122
|
+
resumed_successes: resumedSuccesses,
|
|
2123
|
+
},
|
|
2124
|
+
actions,
|
|
2125
|
+
artifacts: {
|
|
2126
|
+
approval_record: approvalPath,
|
|
2127
|
+
apply_progress: progressPath,
|
|
2128
|
+
execution_log: executionLogPath,
|
|
2129
|
+
inbound_reference_barrier: inboundBarrierPath,
|
|
2130
|
+
commit_report: path.join(planDir, 'commit-report.json'),
|
|
2131
|
+
attempt_report: attemptPath,
|
|
2132
|
+
},
|
|
2133
|
+
database_audit: {
|
|
2134
|
+
rpc_transaction_log: 'public.command_audit_log',
|
|
2135
|
+
source: 'tiangong-lca dataset maintenance apply',
|
|
2136
|
+
correlation_fields: [
|
|
2137
|
+
'plan_sha256',
|
|
2138
|
+
'operation_id',
|
|
2139
|
+
'action_id',
|
|
2140
|
+
'reason_code',
|
|
2141
|
+
'desired_sha256',
|
|
2142
|
+
],
|
|
2143
|
+
},
|
|
2144
|
+
};
|
|
2145
|
+
writeImmutableJson(attemptPath, report);
|
|
2146
|
+
writeJsonArtifact(report.artifacts.commit_report, report);
|
|
2147
|
+
return report;
|
|
2148
|
+
}
|
|
1287
2149
|
const ordered = [...plan.actions].sort((left, right) => {
|
|
1288
2150
|
const rank = {
|
|
1289
2151
|
save_draft: 0,
|
|
@@ -1426,25 +2288,36 @@ export const __testInternals = {
|
|
|
1426
2288
|
aliasExchangeProgressKey,
|
|
1427
2289
|
appendAliasSuccessLogs,
|
|
1428
2290
|
appendAliasProofProgress,
|
|
2291
|
+
appendParallelDeleteExecutionEntry,
|
|
2292
|
+
assertNoVisibleProcessInboundReferences,
|
|
1429
2293
|
assertApplyPreconditions,
|
|
1430
2294
|
assertAliasSupportSnapshots,
|
|
2295
|
+
assertGlobalInboundProofOptionShape,
|
|
2296
|
+
assertParallelDeletePlan,
|
|
1431
2297
|
buildAliasBatchRequest,
|
|
1432
2298
|
buildAliasPlanRequest,
|
|
1433
2299
|
clock,
|
|
1434
2300
|
errorMessage,
|
|
1435
2301
|
executeDerivativeAdmission,
|
|
1436
|
-
executeAliasPlan,
|
|
1437
2302
|
executeAction,
|
|
2303
|
+
executeParallelDeletePlan,
|
|
1438
2304
|
finalProjectedRows,
|
|
1439
2305
|
loadDesiredPayload,
|
|
1440
2306
|
nextAttemptPath,
|
|
2307
|
+
nextParallelDeleteInboundBarrierPath,
|
|
2308
|
+
normalizeMaintenanceMaxParallel,
|
|
2309
|
+
parallelDeleteDesiredSha256,
|
|
2310
|
+
parallelDeleteTargetBindingSha256,
|
|
1441
2311
|
parseDerivativeSubmitProgress,
|
|
1442
2312
|
validateDerivativeAdmissionAttempt,
|
|
1443
2313
|
parseAliasBatchProgress,
|
|
1444
2314
|
parseAliasPlanProgress,
|
|
1445
2315
|
parseProgress,
|
|
2316
|
+
parseParallelDeleteExecutionLog,
|
|
2317
|
+
remoteAuditId,
|
|
1446
2318
|
validateAliasRpcResult,
|
|
1447
2319
|
validateAliasPlanRpcResult,
|
|
1448
2320
|
validateApprovalRecord,
|
|
2321
|
+
validateParallelDeleteGlobalInboundProof,
|
|
1449
2322
|
};
|
|
1450
2323
|
//# sourceMappingURL=dataset-maintenance-apply.js.map
|