61 lines
2.7 KiB
JavaScript
61 lines
2.7 KiB
JavaScript
import crypto from "node:crypto";
|
|
|
|
export const COMMAND_BUDGET = Object.freeze({
|
|
maxCommands: 48,
|
|
maxReadCommands: 16,
|
|
});
|
|
|
|
/** Return the explicit source/RNA record IDs selected for one module batch. */
|
|
export function selectedRecordIds(module = {}) {
|
|
const values = module.selectedRecordIds ?? module.coverageBindings ?? module.sourceUnits ?? [];
|
|
return [...new Set((Array.isArray(values) ? values : []).filter((value) => typeof value === "string" && value.length > 0))];
|
|
}
|
|
|
|
/** A batch identity is stable across retries and changes when selection changes. */
|
|
export function batchIdFor(task, module = {}) {
|
|
const ids = selectedRecordIds(module);
|
|
const moduleId = module.moduleId ?? "UNKNOWN_MODULE";
|
|
const digest = crypto.createHash("sha256").update(JSON.stringify(ids)).digest("hex").slice(0, 16);
|
|
return module.batchId ?? `${task}:${moduleId}:${digest}`;
|
|
}
|
|
|
|
export function batchContract(task, module = {}) {
|
|
const selected = selectedRecordIds(module);
|
|
return {
|
|
batchId: batchIdFor(task, module),
|
|
selectedRecordIds: selected,
|
|
selectedCount: selected.length,
|
|
};
|
|
}
|
|
|
|
export function completedBatch(manifest, task, module = {}) {
|
|
const expected = batchContract(task, module);
|
|
return manifest?.status === "done"
|
|
&& manifest.batchId === expected.batchId
|
|
&& JSON.stringify(manifest.selectedRecordIds ?? []) === JSON.stringify(expected.selectedRecordIds);
|
|
}
|
|
|
|
export function commandBudget(commands = []) {
|
|
const values = Array.isArray(commands) ? commands.filter((value) => typeof value === "string") : [];
|
|
const readCommands = values.filter((value) => /\b(?:cat|head|tail|sed|rg|find|ls|stat)\b/u.test(value));
|
|
return {
|
|
maxCommands: COMMAND_BUDGET.maxCommands,
|
|
maxReadCommands: COMMAND_BUDGET.maxReadCommands,
|
|
commandCount: values.length,
|
|
readCommandCount: readCommands.length,
|
|
withinBudget: values.length <= COMMAND_BUDGET.maxCommands && readCommands.length <= COMMAND_BUDGET.maxReadCommands,
|
|
};
|
|
}
|
|
|
|
export function adaptiveBatchSize({ currentSize, history = [], minSize = 1, maxSize = currentSize, remaining = Number.MAX_SAFE_INTEGER }) {
|
|
const size = Math.max(minSize, Math.min(maxSize, currentSize));
|
|
const recent = history.filter((item) => Number.isFinite(item.durationMs) && Number.isFinite(item.commands)).slice(-3);
|
|
let next = size;
|
|
if (recent.some((item) => item.timeout === true || item.durationMs > 900_000 || item.commands > COMMAND_BUDGET.maxCommands)) {
|
|
next = Math.floor(size / 2);
|
|
} else if (recent.length > 0 && recent.every((item) => item.durationMs < 300_000 && item.commands < 24 && item.focusedGateFailure !== true)) {
|
|
next = size + 4;
|
|
}
|
|
return Math.max(minSize, Math.min(maxSize, remaining, next));
|
|
}
|