@tiangong-lca/cli 0.0.31 → 0.0.32
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.
|
@@ -908,6 +908,42 @@ async function readbackIsDesiredExact(options) {
|
|
|
908
908
|
return false;
|
|
909
909
|
}
|
|
910
910
|
}
|
|
911
|
+
function normalizeExecutionMaxParallel(value) {
|
|
912
|
+
const normalized = value ?? 1;
|
|
913
|
+
if (!Number.isInteger(normalized) || normalized < 1 || normalized > 8) {
|
|
914
|
+
executionContractError('Execution contract --max-parallel must be an integer from 1 to 8.');
|
|
915
|
+
}
|
|
916
|
+
return normalized;
|
|
917
|
+
}
|
|
918
|
+
function executionSerialPrefixLength(contract) {
|
|
919
|
+
const actionIndexes = new Map(contract.actions.map((action, index) => [action.action_id, index]));
|
|
920
|
+
let highestDependencyIndex = -1;
|
|
921
|
+
for (const action of contract.actions) {
|
|
922
|
+
for (const dependencyActionId of action.dependency_action_ids) {
|
|
923
|
+
highestDependencyIndex = Math.max(highestDependencyIndex, actionIndexes.get(dependencyActionId));
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return highestDependencyIndex + 1;
|
|
927
|
+
}
|
|
928
|
+
function assertParallelSuffixTargetsAreUnique(contract, serialPrefixLength) {
|
|
929
|
+
const targets = new Set();
|
|
930
|
+
for (const action of contract.actions.slice(serialPrefixLength)) {
|
|
931
|
+
const target = `${action.table}\u0000${action.id}\u0000${action.version}`;
|
|
932
|
+
if (targets.has(target)) {
|
|
933
|
+
executionContractError('Execution contract parallel suffix contains a repeated table/id/version target.');
|
|
934
|
+
}
|
|
935
|
+
targets.add(target);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
async function renewExecutionOwnerToken(options) {
|
|
939
|
+
const accessToken = await options.runtime.getAccessToken();
|
|
940
|
+
const actor = decodeExecutionActor(accessToken);
|
|
941
|
+
if (actor.user_id !== options.contract.owner.user_id ||
|
|
942
|
+
actor.email !== options.contract.owner.email) {
|
|
943
|
+
executionContractError('Renewed owner session does not match the execution contract.');
|
|
944
|
+
}
|
|
945
|
+
options.commandTransport.accessToken = accessToken;
|
|
946
|
+
}
|
|
911
947
|
async function runExecutionContractBatch(options) {
|
|
912
948
|
const contractSha256 = sha256Json(options.contract);
|
|
913
949
|
const ledgerRoot = executionLedgerRoot(options.env, options.contract);
|
|
@@ -919,11 +955,20 @@ async function runExecutionContractBatch(options) {
|
|
|
919
955
|
executionContractError('Owner session or project does not match the execution contract.');
|
|
920
956
|
}
|
|
921
957
|
options.files.execution_ledger = ledgerRoot;
|
|
922
|
-
const
|
|
958
|
+
const serialPrefixLength = executionSerialPrefixLength(options.contract);
|
|
959
|
+
if (options.maxParallel > 1) {
|
|
960
|
+
assertParallelSuffixTargetsAreUnique(options.contract, serialPrefixLength);
|
|
961
|
+
}
|
|
962
|
+
const reports = new Array(options.contract.actions.length);
|
|
923
963
|
const statuses = new Map();
|
|
924
964
|
const referenceOnlySupportCache = new Map();
|
|
925
|
-
|
|
965
|
+
const executeAction = async (index) => {
|
|
966
|
+
const action = options.contract.actions[index];
|
|
926
967
|
const row = options.preparedRows[index];
|
|
968
|
+
const storeReport = (report) => {
|
|
969
|
+
reports[index] = report;
|
|
970
|
+
statuses.set(action.action_id, report.status);
|
|
971
|
+
};
|
|
927
972
|
const preparedFailure = buildPreparedFailure(row, options.allowReferenceOnlySupport);
|
|
928
973
|
if (preparedFailure) {
|
|
929
974
|
const report = {
|
|
@@ -934,9 +979,8 @@ async function runExecutionContractBatch(options) {
|
|
|
934
979
|
replayed: false,
|
|
935
980
|
readback: 'not_performed',
|
|
936
981
|
};
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
continue;
|
|
982
|
+
storeReport(report);
|
|
983
|
+
return;
|
|
940
984
|
}
|
|
941
985
|
const priorOutcome = ledger.outcomes.get(action.action_id);
|
|
942
986
|
if (priorOutcome) {
|
|
@@ -965,9 +1009,8 @@ async function runExecutionContractBatch(options) {
|
|
|
965
1009
|
}
|
|
966
1010
|
: {}),
|
|
967
1011
|
});
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
continue;
|
|
1012
|
+
storeReport(report);
|
|
1013
|
+
return;
|
|
971
1014
|
}
|
|
972
1015
|
if (ledger.attempts.has(action.action_id)) {
|
|
973
1016
|
const report = await finalizeAttemptedAction({
|
|
@@ -982,9 +1025,8 @@ async function runExecutionContractBatch(options) {
|
|
|
982
1025
|
now: options.now,
|
|
983
1026
|
recovered: true,
|
|
984
1027
|
});
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
continue;
|
|
1028
|
+
storeReport(report);
|
|
1029
|
+
return;
|
|
988
1030
|
}
|
|
989
1031
|
const blockingDependencies = action.dependency_action_ids.filter((dependency) => statuses.get(dependency) !== 'executed');
|
|
990
1032
|
if (blockingDependencies.length > 0) {
|
|
@@ -1000,9 +1042,8 @@ async function runExecutionContractBatch(options) {
|
|
|
1000
1042
|
details: { dependency_action_ids: blockingDependencies },
|
|
1001
1043
|
},
|
|
1002
1044
|
});
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
continue;
|
|
1045
|
+
storeReport(report);
|
|
1046
|
+
return;
|
|
1006
1047
|
}
|
|
1007
1048
|
let beforeRows;
|
|
1008
1049
|
let transportFailed = false;
|
|
@@ -1048,6 +1089,11 @@ async function runExecutionContractBatch(options) {
|
|
|
1048
1089
|
});
|
|
1049
1090
|
}
|
|
1050
1091
|
}
|
|
1092
|
+
await renewExecutionOwnerToken({
|
|
1093
|
+
runtime: options.runtime,
|
|
1094
|
+
commandTransport: options.commandTransport,
|
|
1095
|
+
contract: options.contract,
|
|
1096
|
+
});
|
|
1051
1097
|
}
|
|
1052
1098
|
catch (error) {
|
|
1053
1099
|
const report = contractRowReport({
|
|
@@ -1059,9 +1105,8 @@ async function runExecutionContractBatch(options) {
|
|
|
1059
1105
|
readback: 'not_performed',
|
|
1060
1106
|
error: serializeError(error),
|
|
1061
1107
|
});
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
continue;
|
|
1108
|
+
storeReport(report);
|
|
1109
|
+
return;
|
|
1065
1110
|
}
|
|
1066
1111
|
try {
|
|
1067
1112
|
const beforeDispatch = () => {
|
|
@@ -1117,15 +1162,40 @@ async function runExecutionContractBatch(options) {
|
|
|
1117
1162
|
now: options.now,
|
|
1118
1163
|
recovered: transportFailed,
|
|
1119
1164
|
});
|
|
1120
|
-
|
|
1121
|
-
|
|
1165
|
+
storeReport(report);
|
|
1166
|
+
};
|
|
1167
|
+
for (let index = 0; index < serialPrefixLength; index += 1) {
|
|
1168
|
+
await executeAction(index);
|
|
1122
1169
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1170
|
+
let nextParallelIndex = serialPrefixLength;
|
|
1171
|
+
let fatalWorkerError = null;
|
|
1172
|
+
const runParallelWorker = async () => {
|
|
1173
|
+
while (fatalWorkerError === null) {
|
|
1174
|
+
const index = nextParallelIndex;
|
|
1175
|
+
nextParallelIndex += 1;
|
|
1176
|
+
if (index >= options.contract.actions.length) {
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
try {
|
|
1180
|
+
await executeAction(index);
|
|
1181
|
+
}
|
|
1182
|
+
catch (error) {
|
|
1183
|
+
fatalWorkerError ??= error;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
const parallelWorkerCount = Math.min(options.maxParallel, options.contract.actions.length - serialPrefixLength);
|
|
1188
|
+
await Promise.all(Array.from({ length: parallelWorkerCount }, () => runParallelWorker()));
|
|
1189
|
+
if (fatalWorkerError !== null) {
|
|
1190
|
+
throw fatalWorkerError;
|
|
1191
|
+
}
|
|
1192
|
+
const completedReports = reports;
|
|
1193
|
+
const failures = completedReports.filter((row) => ['failed', 'unknown', 'blocked'].includes(row.status));
|
|
1194
|
+
writeJsonLinesArtifact(options.files.progress_jsonl, completedReports);
|
|
1125
1195
|
writeJsonLinesArtifact(options.files.failures_jsonl, failures);
|
|
1126
|
-
const unknown =
|
|
1127
|
-
const failed =
|
|
1128
|
-
const blocked =
|
|
1196
|
+
const unknown = completedReports.filter((row) => row.status === 'unknown').length;
|
|
1197
|
+
const failed = completedReports.filter((row) => row.status === 'failed').length;
|
|
1198
|
+
const blocked = completedReports.filter((row) => row.status === 'blocked').length;
|
|
1129
1199
|
const report = {
|
|
1130
1200
|
schema_version: 2,
|
|
1131
1201
|
generated_at_utc: options.now(),
|
|
@@ -1142,21 +1212,24 @@ async function runExecutionContractBatch(options) {
|
|
|
1142
1212
|
counts: {
|
|
1143
1213
|
selected: options.preparedRows.length,
|
|
1144
1214
|
prepared: 0,
|
|
1145
|
-
executed:
|
|
1215
|
+
executed: completedReports.filter((row) => row.status === 'executed').length,
|
|
1146
1216
|
failed,
|
|
1147
1217
|
unknown,
|
|
1148
1218
|
blocked,
|
|
1149
1219
|
attempts_consumed: ledger.attempts.size,
|
|
1150
1220
|
by_table: byTable(options.preparedRows),
|
|
1151
|
-
operations: operationCount(
|
|
1221
|
+
operations: operationCount(completedReports),
|
|
1152
1222
|
},
|
|
1153
1223
|
files: options.files,
|
|
1154
|
-
rows:
|
|
1224
|
+
rows: completedReports,
|
|
1155
1225
|
execution_contract: {
|
|
1156
1226
|
path: path.resolve(options.contractPath),
|
|
1157
1227
|
sha256: contractSha256,
|
|
1158
1228
|
execution_id: options.contract.execution_id,
|
|
1159
1229
|
target_mode: 'owner_draft',
|
|
1230
|
+
max_parallel: options.maxParallel,
|
|
1231
|
+
serial_prefix_actions: serialPrefixLength,
|
|
1232
|
+
parallel_suffix_actions: options.contract.actions.length - serialPrefixLength,
|
|
1160
1233
|
},
|
|
1161
1234
|
};
|
|
1162
1235
|
writeJsonArtifact(options.files.summary_json, report);
|
|
@@ -1173,6 +1246,7 @@ export async function runDatasetSaveDraft(options) {
|
|
|
1173
1246
|
const files = buildFiles(outDir);
|
|
1174
1247
|
const preparedRows = prepareRows(inputPath, options.rawInput, requestedType);
|
|
1175
1248
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1249
|
+
const maxParallel = normalizeExecutionMaxParallel(options.maxParallel);
|
|
1176
1250
|
const executionContractPath = options.executionContractPath
|
|
1177
1251
|
? path.resolve(options.executionContractPath)
|
|
1178
1252
|
: null;
|
|
@@ -1182,6 +1256,9 @@ export async function runDatasetSaveDraft(options) {
|
|
|
1182
1256
|
if (executionContract && !commit) {
|
|
1183
1257
|
executionContractError('Execution contract mode requires --commit.');
|
|
1184
1258
|
}
|
|
1259
|
+
if (!executionContract && maxParallel !== 1) {
|
|
1260
|
+
executionContractError('--max-parallel greater than 1 requires --execution-contract.');
|
|
1261
|
+
}
|
|
1185
1262
|
if (executionContract) {
|
|
1186
1263
|
bindExecutionContractRows(executionContract, preparedRows);
|
|
1187
1264
|
}
|
|
@@ -1227,6 +1304,7 @@ export async function runDatasetSaveDraft(options) {
|
|
|
1227
1304
|
fetchImpl: options.fetchImpl,
|
|
1228
1305
|
timeoutMs,
|
|
1229
1306
|
now: () => now.toISOString(),
|
|
1307
|
+
maxParallel,
|
|
1230
1308
|
});
|
|
1231
1309
|
}
|
|
1232
1310
|
const reports = [];
|