feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
+263
View File
@@ -0,0 +1,263 @@
#!/usr/bin/env node
require('ts-node/register/transpile-only');
const fs = require('node:fs');
const path = require('node:path');
const {
CompletionReceiptOrphanFileDirectory,
} = require('../back/runtime/adapters/fs/completionReceiptOrphanDirectory');
const {
CompletionReceiptOrphanAuditor,
} = require('../back/runtime/application/completionReceiptOrphanAuditor');
const ATTEMPT_STATUSES = new Set([
'claimed',
'starting',
'running',
'succeeded',
'failed',
'cancelled',
'timed_out',
'lost',
]);
const JOURNAL_STATES = new Set(['pending', 'quarantined']);
function numericArgument(name, value, minimum, maximum) {
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
throw new Error(`${name} must be between ${minimum} and ${maximum}`);
}
return parsed;
}
function parseArguments(argv) {
const options = {
database: path.resolve(process.cwd(), 'data/db/database.sqlite'),
root: path.resolve(process.cwd(), 'data/runtime/completion-receipts'),
mode: 'audit',
startShard: 0,
shardCount: 8,
maxEntriesPerShard: 32,
minimumAgeMs: 5 * 60_000,
json: false,
failOnFindings: false,
};
for (const argument of argv) {
if (argument === '--') continue;
if (argument === '--json') {
options.json = true;
} else if (argument === '--quarantine') {
options.mode = 'quarantine';
} else if (argument === '--fail-on-findings') {
options.failOnFindings = true;
} else if (argument.startsWith('--database=')) {
const value = argument.slice('--database='.length);
if (!value) throw new Error('--database must not be empty');
options.database = path.resolve(value);
} else if (argument.startsWith('--root=')) {
const value = argument.slice('--root='.length);
if (!value) throw new Error('--root must not be empty');
options.root = path.resolve(value);
} else if (argument.startsWith('--start-shard=')) {
const value = argument.slice('--start-shard='.length);
if (!/^[0-9a-f]{2}$/.test(value)) {
throw new Error('--start-shard must be two lowercase hex digits');
}
options.startShard = Number.parseInt(value, 16);
} else if (argument.startsWith('--shards=')) {
options.shardCount = numericArgument(
'--shards',
argument.slice('--shards='.length),
1,
32,
);
} else if (argument.startsWith('--entries-per-shard=')) {
options.maxEntriesPerShard = numericArgument(
'--entries-per-shard',
argument.slice('--entries-per-shard='.length),
1,
64,
);
} else if (argument.startsWith('--minimum-age-ms=')) {
options.minimumAgeMs = numericArgument(
'--minimum-age-ms',
argument.slice('--minimum-age-ms='.length),
0,
Number.MAX_SAFE_INTEGER,
);
} else {
throw new Error(`Unsupported argument: ${argument}`);
}
}
return options;
}
class SqliteCompletionReceiptOwnershipSource {
constructor(database) {
this.database = database;
}
async lookup(attemptIds) {
const ownership = new Map();
for (let offset = 0; offset < attemptIds.length; offset += 32) {
const chunk = attemptIds.slice(offset, offset + 32);
if (chunk.length === 0) continue;
const placeholders = chunk.map(() => '?').join(', ');
const journalRows = this.database
.prepare(
`SELECT attempt_id, state AS journal_state
FROM CompletionReceiptJournals
WHERE attempt_id IN (${placeholders})`,
)
.all(...chunk);
for (const row of journalRows) {
if (!JOURNAL_STATES.has(row.journal_state)) {
throw new Error(
`Receipt journal ${row.attempt_id} has an invalid state`,
);
}
ownership.set(row.attempt_id, {
attemptId: row.attempt_id,
journalState: row.journal_state,
});
}
const attemptRows = this.database
.prepare(
`SELECT id AS attempt_id, status AS attempt_status
FROM RunAttempts
WHERE id IN (${placeholders})`,
)
.all(...chunk);
for (const row of attemptRows) {
if (!ATTEMPT_STATUSES.has(row.attempt_status)) {
throw new Error(
`Run Attempt ${row.attempt_id} has an invalid status`,
);
}
ownership.set(row.attempt_id, {
...ownership.get(row.attempt_id),
attemptId: row.attempt_id,
attemptStatus: row.attempt_status,
});
}
}
return ownership;
}
}
function assertRequiredSchema(database) {
const rows = database
.prepare(
`SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name IN ('RunAttempts', 'CompletionReceiptJournals')`,
)
.all();
const names = new Set(rows.map((row) => row.name));
for (const required of ['RunAttempts', 'CompletionReceiptJournals']) {
if (!names.has(required)) {
throw new Error(
`Database is not ready for receipt audit: missing ${required}`,
);
}
}
}
function findingCount(report) {
const findingCategories = new Set([
'terminal_orphan',
'unknown_receipt',
'stale_temporary',
'unknown_entry',
'unsafe_entry',
]);
return (
report.entries.filter((entry) => findingCategories.has(entry.category))
.length + report.overflowShards.length
);
}
function renderText(report, options) {
const lines = [
`QingLong 3.0 receipt audit: ${report.mode}`,
`root: ${options.root}`,
`shards: ${report.startShard} -> ${report.nextShard}`,
`scanned entries: ${report.scannedEntries}`,
`overflow shards: ${report.overflowShards.join(', ') || 'none'}`,
];
for (const [category, count] of Object.entries(report.counts)) {
if (count > 0) lines.push(`${category}: ${count}`);
}
for (const entry of report.entries) {
if (entry.action !== 'retained') {
lines.push(
`${JSON.stringify(`${entry.shard}/${entry.name}`)}: ${
entry.category
} (${entry.action})`,
);
}
}
return `${lines.join('\n')}\n`;
}
async function main() {
const options = parseArguments(process.argv.slice(2));
const nodeMajor = Number(process.versions.node.split('.')[0]);
if (nodeMajor < 24) {
throw new Error('ql3-receipt-audit requires Node.js 24 or newer');
}
if (!fs.existsSync(options.database)) {
throw new Error(`Database does not exist: ${options.database}`);
}
const { DatabaseSync } = require('node:sqlite');
const database = new DatabaseSync(options.database, {
allowExtension: false,
allowUnknownNamedParameters: false,
defensive: true,
enableDoubleQuotedStringLiterals: false,
enableForeignKeyConstraints: true,
readOnly: true,
timeout: 1_000,
});
try {
assertRequiredSchema(database);
const auditor = new CompletionReceiptOrphanAuditor(
new CompletionReceiptOrphanFileDirectory(options.root),
new SqliteCompletionReceiptOwnershipSource(database),
);
const report = await auditor.run(options);
process.stdout.write(
options.json
? `${JSON.stringify({
...report,
database: path.basename(options.database),
root: options.root,
})}\n`
: renderText(report, options),
);
if (options.failOnFindings && findingCount(report) > 0) {
process.exitCode = 1;
}
} finally {
database.close();
}
}
if (require.main === module) {
main().catch((error) => {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
});
}
module.exports = {
SqliteCompletionReceiptOwnershipSource,
assertRequiredSchema,
findingCount,
parseArguments,
renderText,
};