feat(ql3): compare bounded task run outcomes

This commit is contained in:
whyour
2026-08-14 17:18:25 +08:00
parent a55613afaa
commit 828370c60b
22 changed files with 1802 additions and 22 deletions
@@ -0,0 +1,328 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
BUILTIN_TASK_RUN_OUTCOME_COMPARE_ADAPTER,
BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL,
BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL_DEFINITION,
BuiltInTaskRunOutcomeCompareToolAdapter,
BuiltInTaskRunOutcomeCompareToolUnavailableError,
InvalidBuiltInTaskRunOutcomeCompareToolError,
TASK_RUN_OUTCOME_SEARCH_LIMIT,
createBuiltInTaskRunOutcomeCompareToolHandlerBinding,
executeBuiltInTaskRunOutcomeCompareTool,
} = require('../dist/tool-execution/builtin-run-compare/builtInTaskRunOutcomeCompareTool');
const {
createPluginPackageResourceGenerationFromReferences,
} = require('../dist/plugin-package/pluginPackageResourceGeneration');
const {
createProjectToolDefinitionSnapshot,
projectToolDefinitionRegistry,
} = require('../dist/tool-execution/tool-registry/projectToolDefinitionSnapshot');
const DIGEST_A = 'a'.repeat(64);
const DIGEST_B = 'b'.repeat(64);
const DIGEST_C = 'c'.repeat(64);
function run(id, overrides = {}) {
return {
id,
projectId: 'project-outcomes',
taskId: 'task-backup',
taskRevision: 'task-backup@4',
triggerType: 'schedule',
executionOrigin: 'scheduled_system',
executionOwner: 'runtime',
status: 'succeeded',
version: 4,
eventSequence: 8,
priority: 10,
createdAtMs: 1_000,
queuedAtMs: 1_020,
startedAtMs: 1_050,
finishedAtMs: 1_150,
requestId: 'must-not-cross-tool-output',
...overrides,
};
}
function windowRecord(value) {
return {
id: value.id,
projectId: value.projectId,
taskId: value.taskId,
status: value.status,
createdAtMs: value.createdAtMs,
};
}
function fixture(records, window, calls = []) {
return {
windows: {
async listRecentRunsByTask(query) {
calls.push({ type: 'window', query });
return window;
},
},
runs: {
async findRunById(runId) {
calls.push({ type: 'point', runId });
return records.get(runId) ?? null;
},
},
};
}
function snapshot(
definition = BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL_DEFINITION,
) {
const generation = createPluginPackageResourceGenerationFromReferences({
installationId: 'install-qinglong-outcome-compare',
projectId: 'project-outcomes',
packageName: 'qinglong',
lockDigest: DIGEST_A,
generation: 1,
previousActiveLockDigest: null,
contentDigest: DIGEST_B,
resources: [],
});
return createProjectToolDefinitionSnapshot({
projectId: 'project-outcomes',
contributions: [
{
generation,
revisionDigest: DIGEST_C,
definitions: [definition],
},
],
});
}
test('selects and compares the latest succeeded and failed Runs in one fixed Task window', async () => {
const calls = [];
const succeeded = run('run-success');
const failed = run('run-failure', {
taskRevision: 'task-backup@5',
status: 'failed',
version: 6,
eventSequence: 12,
createdAtMs: 2_000,
queuedAtMs: 2_040,
startedAtMs: 2_100,
finishedAtMs: 2_350,
});
const ignored = run('run-running', {
status: 'running',
createdAtMs: 3_000,
});
const value = fixture(
new Map([
[succeeded.id, succeeded],
[failed.id, failed],
]),
[windowRecord(ignored), windowRecord(failed), windowRecord(succeeded)],
calls,
);
const output = await executeBuiltInTaskRunOutcomeCompareTool(
value.windows,
value.runs,
'project-outcomes',
{ taskId: 'task-backup' },
);
assert.deepEqual(calls, [
{
type: 'window',
query: {
projectId: 'project-outcomes',
taskId: 'task-backup',
limit: 65,
},
},
{ type: 'point', runId: 'run-success' },
{ type: 'point', runId: 'run-failure' },
]);
assert.equal(output.taskId, 'task-backup');
assert.equal(output.baselineOutcome, 'succeeded');
assert.equal(output.candidateOutcome, 'failed');
assert.equal(output.baseline.id, 'run-success');
assert.equal(output.candidate.id, 'run-failure');
assert.deepEqual(output.changedFields, ['taskRevision', 'status']);
assert.equal(output.queueDelayDeltaMs, 20);
assert.equal(output.executionDurationDeltaMs, 150);
assert.equal(output.totalDurationDeltaMs, 200);
assert.deepEqual(output.selection, {
windowLimit: 64,
searchedRunCount: 3,
hasOlderRuns: false,
complete: true,
order: 'created_at_desc_id_desc',
});
assert.equal(
output.consistency,
'bounded_task_window_then_ordered_point_reads',
);
assert.equal(output.baseline.requestId, undefined);
const registry = projectToolDefinitionRegistry(snapshot());
assert.deepEqual(
registry.normalizeOutput(
BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL.name,
BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL.version,
output,
),
output,
);
});
test('reports an incomplete fixed window without exposing pagination', async () => {
const calls = [];
const rows = Array.from(
{ length: TASK_RUN_OUTCOME_SEARCH_LIMIT + 1 },
(_, index) =>
windowRecord(
run(`run-${String(100 - index).padStart(3, '0')}`, {
status: 'running',
createdAtMs: 10_000 - index,
}),
),
);
const value = fixture(new Map(), rows, calls);
const output = await executeBuiltInTaskRunOutcomeCompareTool(
value.windows,
value.runs,
'project-outcomes',
{ taskId: 'task-backup' },
);
assert.deepEqual(output.baseline, { found: false });
assert.deepEqual(output.candidate, { found: false });
assert.equal(output.comparable, false);
assert.deepEqual(output.selection, {
windowLimit: 64,
searchedRunCount: 64,
hasOlderRuns: true,
complete: false,
order: 'created_at_desc_id_desc',
});
assert.equal(calls.length, 1);
assert.equal(
BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL_DEFINITION.inputSchema.properties
.after,
undefined,
);
assert.equal(
BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL_DEFINITION.inputSchema.properties
.limit,
undefined,
);
});
test('fails closed on foreign, unordered, corrupt, disappearing, and unavailable records', async () => {
const valid = windowRecord(run('run-valid'));
for (const window of [
[{ ...valid, projectId: 'other-project' }],
[valid, { ...valid, id: 'run-newer', createdAtMs: 2_000 }],
[{ ...valid, status: 'invented' }],
]) {
const value = fixture(new Map(), window);
await assert.rejects(
executeBuiltInTaskRunOutcomeCompareTool(
value.windows,
value.runs,
'project-outcomes',
{ taskId: 'task-backup' },
),
BuiltInTaskRunOutcomeCompareToolUnavailableError,
);
}
const missing = fixture(new Map(), [valid]);
await assert.rejects(
executeBuiltInTaskRunOutcomeCompareTool(
missing.windows,
missing.runs,
'project-outcomes',
{ taskId: 'task-backup' },
),
BuiltInTaskRunOutcomeCompareToolUnavailableError,
);
await assert.rejects(
executeBuiltInTaskRunOutcomeCompareTool(
{
async listRecentRunsByTask() {
throw new Error('private DSN must not escape');
},
},
fixture(new Map(), []).runs,
'project-outcomes',
{ taskId: 'task-backup' },
),
BuiltInTaskRunOutcomeCompareToolUnavailableError,
);
});
test('rejects aliases and binds the reviewed retry-safe database-read adapter', async () => {
const empty = fixture(new Map(), []);
await assert.rejects(
executeBuiltInTaskRunOutcomeCompareTool(
empty.windows,
empty.runs,
'project-outcomes',
{ taskId: 'task-backup', after: 'cursor' },
),
InvalidBuiltInTaskRunOutcomeCompareToolError,
);
const currentSnapshot = snapshot();
const binding = createBuiltInTaskRunOutcomeCompareToolHandlerBinding(
currentSnapshot,
['edge', 'standalone', 'cluster-control'],
);
assert.deepEqual(binding.tool, BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL);
assert.deepEqual(binding.adapter, BUILTIN_TASK_RUN_OUTCOME_COMPARE_ADAPTER);
assert.deepEqual(binding.authorities, ['database.read']);
const adapter = new BuiltInTaskRunOutcomeCompareToolAdapter(
binding,
'edge',
projectToolDefinitionRegistry(currentSnapshot),
empty.windows,
empty.runs,
);
assert.equal(adapter.recoveryMode, 'retry_safe_read');
const output = await adapter.execute(
{ projectId: 'project-outcomes' },
{ taskId: 'task-backup' },
);
assert.equal(output.selection.complete, true);
assert.throws(
() =>
new BuiltInTaskRunOutcomeCompareToolAdapter(
{ ...binding, authorities: ['database.read', 'network.client'] },
'edge',
projectToolDefinitionRegistry(currentSnapshot),
empty.windows,
empty.runs,
),
/handler authority is invalid/,
);
});
test('publishes only explicit outcome comparison subpaths and keeps the root unchanged', () => {
const tool = require('@qinglong/runtime-core/builtin-task-run-outcome-compare-tool');
const projection = require('@qinglong/runtime-core/builtin-task-run-outcome-compare-projection');
const window = require('@qinglong/runtime-core/task-run-outcome-window');
const root = require('@qinglong/runtime-core');
assert.equal(
tool.BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL.name,
'qinglong.task.runs.compare',
);
assert.equal(projection.TASK_RUN_OUTCOME_SEARCH_LIMIT, 64);
assert.equal(window.MAX_TASK_RUN_OUTCOME_WINDOW_STORAGE_LIMIT, 65);
assert.equal(root.BUILTIN_TASK_RUN_OUTCOME_COMPARE_TOOL, undefined);
assert.equal(root.executeBuiltInTaskRunOutcomeCompareTool, undefined);
});