- runner: QA_HEARTBEAT_MIN (default 60) in-process timer runs a scheduled QA pass; one pulse ~90s after boot. Triage (LLM) skipped on clean scheduled runs to save tokens; only manual runs or runs with errors get a written summary. - qa-finish.php prunes to the 50 most recent runs. - systemd unit exposes QA_HEARTBEAT_MIN for tuning.
71 lines
2.6 KiB
PHP
71 lines
2.6 KiB
PHP
<?php
|
|
/**
|
|
* Close a QA run: bulk-insert its findings and mark the run terminal. Called by
|
|
* the console runner after the crawl. Findings are read from a temp JSON file
|
|
* (not an arg) to stay clear of ARG_MAX.
|
|
*
|
|
* php api/cli/qa-finish.php --run=42 --status=done \
|
|
* --summary="…" --counts='{"error":2,"warning":5,"info":10}' \
|
|
* --findings-file=/tmp/qa-42.json
|
|
*
|
|
* findings JSON = [{ check_type, severity, url, detail, fix_hint }, ...]
|
|
*/
|
|
|
|
require __DIR__ . '/../vendor/autoload.php';
|
|
require __DIR__ . '/../config.php';
|
|
|
|
$opts = getopt('', ['run:', 'status:', 'summary::', 'counts::', 'findings-file::']);
|
|
$runId = (int) ($opts['run'] ?? 0);
|
|
$status = in_array(($opts['status'] ?? ''), ['done', 'failed'], true) ? $opts['status'] : 'done';
|
|
$summary = (string) ($opts['summary'] ?? '');
|
|
$counts = (string) ($opts['counts'] ?? '{}');
|
|
$file = (string) ($opts['findings-file'] ?? '');
|
|
|
|
if ($runId <= 0) {
|
|
fwrite(STDERR, "usage: qa-finish.php --run=<n> --status=done|failed [--summary --counts --findings-file]\n");
|
|
exit(1);
|
|
}
|
|
if (json_decode($counts) === null && json_last_error() !== JSON_ERROR_NONE) $counts = '{}';
|
|
|
|
$findings = [];
|
|
if ($file !== '' && is_readable($file)) {
|
|
$findings = json_decode((string) file_get_contents($file), true) ?: [];
|
|
}
|
|
|
|
$pdo = new PDO(
|
|
sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DB_HOST, DB_NAME),
|
|
DB_USER,
|
|
DB_PASS,
|
|
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
|
);
|
|
|
|
$allowed = ['error', 'warning', 'info'];
|
|
$ins = $pdo->prepare(
|
|
'INSERT INTO cja_qa_findings (run_id, check_type, severity, url, detail, fix_hint)
|
|
VALUES (?, ?, ?, ?, ?, ?)'
|
|
);
|
|
$n = 0;
|
|
foreach ($findings as $f) {
|
|
$sev = in_array(($f['severity'] ?? ''), $allowed, true) ? $f['severity'] : 'info';
|
|
$ins->execute([
|
|
$runId,
|
|
mb_substr((string) ($f['check_type'] ?? ''), 0, 40),
|
|
$sev,
|
|
mb_substr((string) ($f['url'] ?? ''), 0, 500),
|
|
mb_substr((string) ($f['detail'] ?? ''), 0, 500),
|
|
($f['fix_hint'] ?? null) !== null ? (string) $f['fix_hint'] : null,
|
|
]);
|
|
$n++;
|
|
}
|
|
|
|
$upd = $pdo->prepare('UPDATE cja_qa_runs SET status = ?, summary = ?, counts = ?, finished_at = NOW() WHERE run_id = ?');
|
|
$upd->execute([$status, $summary, $counts, $runId]);
|
|
|
|
// Prune to the 50 most recent runs (the heartbeat runs often).
|
|
$cut = (int) ($pdo->query('SELECT run_id FROM cja_qa_runs ORDER BY run_id DESC LIMIT 1 OFFSET 50')->fetchColumn() ?: 0);
|
|
if ($cut > 0) {
|
|
$pdo->prepare('DELETE FROM cja_qa_findings WHERE run_id < ?')->execute([$cut]);
|
|
$pdo->prepare('DELETE FROM cja_qa_runs WHERE run_id < ?')->execute([$cut]);
|
|
}
|
|
|
|
echo json_encode(['ok' => true, 'run_id' => $runId, 'findings' => $n]);
|