@tiangong-lca/cli 0.0.30 → 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.
|
@@ -446,11 +446,17 @@ function schemaForConfig(config) {
|
|
|
446
446
|
}
|
|
447
447
|
function validatePayload(payload, type, config) {
|
|
448
448
|
const { schema, createEntity } = schemaForConfig(config);
|
|
449
|
-
|
|
449
|
+
// SDK schema/entity validation may apply defaults by mutating its input. Keep validation
|
|
450
|
+
// isolated so execution-contract hashing, dispatch, and readback all use the exact input.
|
|
451
|
+
const validationPayload = structuredClone(payload);
|
|
452
|
+
const outcome = validateSchemaWithDeepFallback(schema, validationPayload, createEntity);
|
|
450
453
|
const processIssues = type === 'process'
|
|
451
|
-
? [
|
|
454
|
+
? [
|
|
455
|
+
...collectProcessRequiredFieldIssues(validationPayload),
|
|
456
|
+
...collectProcessPlaceholderIssues(validationPayload),
|
|
457
|
+
]
|
|
452
458
|
: [];
|
|
453
|
-
const importIssues = type === 'process' ? [] : collectImportContentIssues(
|
|
459
|
+
const importIssues = type === 'process' ? [] : collectImportContentIssues(validationPayload);
|
|
454
460
|
const issues = [
|
|
455
461
|
...outcome.issues.map(normalizeValidationIssue),
|
|
456
462
|
...processIssues,
|
|
@@ -902,6 +908,42 @@ async function readbackIsDesiredExact(options) {
|
|
|
902
908
|
return false;
|
|
903
909
|
}
|
|
904
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
|
+
}
|
|
905
947
|
async function runExecutionContractBatch(options) {
|
|
906
948
|
const contractSha256 = sha256Json(options.contract);
|
|
907
949
|
const ledgerRoot = executionLedgerRoot(options.env, options.contract);
|
|
@@ -913,11 +955,20 @@ async function runExecutionContractBatch(options) {
|
|
|
913
955
|
executionContractError('Owner session or project does not match the execution contract.');
|
|
914
956
|
}
|
|
915
957
|
options.files.execution_ledger = ledgerRoot;
|
|
916
|
-
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);
|
|
917
963
|
const statuses = new Map();
|
|
918
964
|
const referenceOnlySupportCache = new Map();
|
|
919
|
-
|
|
965
|
+
const executeAction = async (index) => {
|
|
966
|
+
const action = options.contract.actions[index];
|
|
920
967
|
const row = options.preparedRows[index];
|
|
968
|
+
const storeReport = (report) => {
|
|
969
|
+
reports[index] = report;
|
|
970
|
+
statuses.set(action.action_id, report.status);
|
|
971
|
+
};
|
|
921
972
|
const preparedFailure = buildPreparedFailure(row, options.allowReferenceOnlySupport);
|
|
922
973
|
if (preparedFailure) {
|
|
923
974
|
const report = {
|
|
@@ -928,9 +979,8 @@ async function runExecutionContractBatch(options) {
|
|
|
928
979
|
replayed: false,
|
|
929
980
|
readback: 'not_performed',
|
|
930
981
|
};
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
continue;
|
|
982
|
+
storeReport(report);
|
|
983
|
+
return;
|
|
934
984
|
}
|
|
935
985
|
const priorOutcome = ledger.outcomes.get(action.action_id);
|
|
936
986
|
if (priorOutcome) {
|
|
@@ -959,9 +1009,8 @@ async function runExecutionContractBatch(options) {
|
|
|
959
1009
|
}
|
|
960
1010
|
: {}),
|
|
961
1011
|
});
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
continue;
|
|
1012
|
+
storeReport(report);
|
|
1013
|
+
return;
|
|
965
1014
|
}
|
|
966
1015
|
if (ledger.attempts.has(action.action_id)) {
|
|
967
1016
|
const report = await finalizeAttemptedAction({
|
|
@@ -976,9 +1025,8 @@ async function runExecutionContractBatch(options) {
|
|
|
976
1025
|
now: options.now,
|
|
977
1026
|
recovered: true,
|
|
978
1027
|
});
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
continue;
|
|
1028
|
+
storeReport(report);
|
|
1029
|
+
return;
|
|
982
1030
|
}
|
|
983
1031
|
const blockingDependencies = action.dependency_action_ids.filter((dependency) => statuses.get(dependency) !== 'executed');
|
|
984
1032
|
if (blockingDependencies.length > 0) {
|
|
@@ -994,9 +1042,8 @@ async function runExecutionContractBatch(options) {
|
|
|
994
1042
|
details: { dependency_action_ids: blockingDependencies },
|
|
995
1043
|
},
|
|
996
1044
|
});
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
continue;
|
|
1045
|
+
storeReport(report);
|
|
1046
|
+
return;
|
|
1000
1047
|
}
|
|
1001
1048
|
let beforeRows;
|
|
1002
1049
|
let transportFailed = false;
|
|
@@ -1042,6 +1089,11 @@ async function runExecutionContractBatch(options) {
|
|
|
1042
1089
|
});
|
|
1043
1090
|
}
|
|
1044
1091
|
}
|
|
1092
|
+
await renewExecutionOwnerToken({
|
|
1093
|
+
runtime: options.runtime,
|
|
1094
|
+
commandTransport: options.commandTransport,
|
|
1095
|
+
contract: options.contract,
|
|
1096
|
+
});
|
|
1045
1097
|
}
|
|
1046
1098
|
catch (error) {
|
|
1047
1099
|
const report = contractRowReport({
|
|
@@ -1053,9 +1105,8 @@ async function runExecutionContractBatch(options) {
|
|
|
1053
1105
|
readback: 'not_performed',
|
|
1054
1106
|
error: serializeError(error),
|
|
1055
1107
|
});
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
continue;
|
|
1108
|
+
storeReport(report);
|
|
1109
|
+
return;
|
|
1059
1110
|
}
|
|
1060
1111
|
try {
|
|
1061
1112
|
const beforeDispatch = () => {
|
|
@@ -1111,15 +1162,40 @@ async function runExecutionContractBatch(options) {
|
|
|
1111
1162
|
now: options.now,
|
|
1112
1163
|
recovered: transportFailed,
|
|
1113
1164
|
});
|
|
1114
|
-
|
|
1115
|
-
|
|
1165
|
+
storeReport(report);
|
|
1166
|
+
};
|
|
1167
|
+
for (let index = 0; index < serialPrefixLength; index += 1) {
|
|
1168
|
+
await executeAction(index);
|
|
1116
1169
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
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);
|
|
1119
1195
|
writeJsonLinesArtifact(options.files.failures_jsonl, failures);
|
|
1120
|
-
const unknown =
|
|
1121
|
-
const failed =
|
|
1122
|
-
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;
|
|
1123
1199
|
const report = {
|
|
1124
1200
|
schema_version: 2,
|
|
1125
1201
|
generated_at_utc: options.now(),
|
|
@@ -1136,21 +1212,24 @@ async function runExecutionContractBatch(options) {
|
|
|
1136
1212
|
counts: {
|
|
1137
1213
|
selected: options.preparedRows.length,
|
|
1138
1214
|
prepared: 0,
|
|
1139
|
-
executed:
|
|
1215
|
+
executed: completedReports.filter((row) => row.status === 'executed').length,
|
|
1140
1216
|
failed,
|
|
1141
1217
|
unknown,
|
|
1142
1218
|
blocked,
|
|
1143
1219
|
attempts_consumed: ledger.attempts.size,
|
|
1144
1220
|
by_table: byTable(options.preparedRows),
|
|
1145
|
-
operations: operationCount(
|
|
1221
|
+
operations: operationCount(completedReports),
|
|
1146
1222
|
},
|
|
1147
1223
|
files: options.files,
|
|
1148
|
-
rows:
|
|
1224
|
+
rows: completedReports,
|
|
1149
1225
|
execution_contract: {
|
|
1150
1226
|
path: path.resolve(options.contractPath),
|
|
1151
1227
|
sha256: contractSha256,
|
|
1152
1228
|
execution_id: options.contract.execution_id,
|
|
1153
1229
|
target_mode: 'owner_draft',
|
|
1230
|
+
max_parallel: options.maxParallel,
|
|
1231
|
+
serial_prefix_actions: serialPrefixLength,
|
|
1232
|
+
parallel_suffix_actions: options.contract.actions.length - serialPrefixLength,
|
|
1154
1233
|
},
|
|
1155
1234
|
};
|
|
1156
1235
|
writeJsonArtifact(options.files.summary_json, report);
|
|
@@ -1167,6 +1246,7 @@ export async function runDatasetSaveDraft(options) {
|
|
|
1167
1246
|
const files = buildFiles(outDir);
|
|
1168
1247
|
const preparedRows = prepareRows(inputPath, options.rawInput, requestedType);
|
|
1169
1248
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1249
|
+
const maxParallel = normalizeExecutionMaxParallel(options.maxParallel);
|
|
1170
1250
|
const executionContractPath = options.executionContractPath
|
|
1171
1251
|
? path.resolve(options.executionContractPath)
|
|
1172
1252
|
: null;
|
|
@@ -1176,6 +1256,9 @@ export async function runDatasetSaveDraft(options) {
|
|
|
1176
1256
|
if (executionContract && !commit) {
|
|
1177
1257
|
executionContractError('Execution contract mode requires --commit.');
|
|
1178
1258
|
}
|
|
1259
|
+
if (!executionContract && maxParallel !== 1) {
|
|
1260
|
+
executionContractError('--max-parallel greater than 1 requires --execution-contract.');
|
|
1261
|
+
}
|
|
1179
1262
|
if (executionContract) {
|
|
1180
1263
|
bindExecutionContractRows(executionContract, preparedRows);
|
|
1181
1264
|
}
|
|
@@ -1221,6 +1304,7 @@ export async function runDatasetSaveDraft(options) {
|
|
|
1221
1304
|
fetchImpl: options.fetchImpl,
|
|
1222
1305
|
timeoutMs,
|
|
1223
1306
|
now: () => now.toISOString(),
|
|
1307
|
+
maxParallel,
|
|
1224
1308
|
});
|
|
1225
1309
|
}
|
|
1226
1310
|
const reports = [];
|