feat(tasks): move task ledger to sqlite and add audit CLI (#57361)

* feat(tasks): move task ledger to sqlite

* feat(tasks): add task run audit command

* style(tasks): normalize audit command formatting

* fix(tasks): address audit summary and sqlite perms

* fix(tasks): avoid duplicate lost audit findings
This commit is contained in:
Vincent Koc
2026-03-29 19:34:51 -07:00
committed by GitHub
parent 6f09a68ae7
commit e57b3618fc
13 changed files with 1071 additions and 78 deletions

View File

@@ -7,6 +7,7 @@ const healthCommand = vi.fn();
const sessionsCommand = vi.fn();
const sessionsCleanupCommand = vi.fn();
const tasksListCommand = vi.fn();
const tasksAuditCommand = vi.fn();
const tasksShowCommand = vi.fn();
const tasksNotifyCommand = vi.fn();
const tasksCancelCommand = vi.fn();
@@ -32,6 +33,7 @@ vi.mock("../../commands/sessions-cleanup.js", () => ({
vi.mock("../../commands/tasks.js", () => ({
tasksListCommand,
tasksAuditCommand,
tasksShowCommand,
tasksNotifyCommand,
tasksCancelCommand,
@@ -67,6 +69,7 @@ describe("registerStatusHealthSessionsCommands", () => {
sessionsCommand.mockResolvedValue(undefined);
sessionsCleanupCommand.mockResolvedValue(undefined);
tasksListCommand.mockResolvedValue(undefined);
tasksAuditCommand.mockResolvedValue(undefined);
tasksShowCommand.mockResolvedValue(undefined);
tasksNotifyCommand.mockResolvedValue(undefined);
tasksCancelCommand.mockResolvedValue(undefined);
@@ -242,6 +245,30 @@ describe("registerStatusHealthSessionsCommands", () => {
);
});
it("runs tasks audit subcommand with filters", async () => {
await runCli([
"tasks",
"--json",
"audit",
"--severity",
"error",
"--code",
"stale_running",
"--limit",
"5",
]);
expect(tasksAuditCommand).toHaveBeenCalledWith(
expect.objectContaining({
json: true,
severity: "error",
code: "stale_running",
limit: 5,
}),
runtime,
);
});
it("runs tasks notify subcommand with lookup and policy forwarding", async () => {
await runCli(["tasks", "notify", "run-123", "state_changes"]);

View File

@@ -4,6 +4,7 @@ import { sessionsCleanupCommand } from "../../commands/sessions-cleanup.js";
import { sessionsCommand } from "../../commands/sessions.js";
import { statusCommand } from "../../commands/status.js";
import {
tasksAuditCommand,
tasksCancelCommand,
tasksListCommand,
tasksNotifyCommand,
@@ -272,6 +273,38 @@ export function registerStatusHealthSessionsCommands(program: Command) {
});
});
tasksCmd
.command("audit")
.description("Show stale or broken background task runs")
.option("--json", "Output as JSON", false)
.option("--severity <level>", "Filter by severity (warn, error)")
.option(
"--code <name>",
"Filter by finding code (stale_queued, stale_running, lost, delivery_failed, missing_cleanup, inconsistent_timestamps)",
)
.option("--limit <n>", "Limit displayed findings")
.action(async (opts, command) => {
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
await runCommandWithRuntime(defaultRuntime, async () => {
await tasksAuditCommand(
{
json: Boolean(opts.json || parentOpts?.json),
severity: opts.severity as "warn" | "error" | undefined,
code: opts.code as
| "stale_queued"
| "stale_running"
| "lost"
| "delivery_failed"
| "missing_cleanup"
| "inconsistent_timestamps"
| undefined,
limit: parsePositiveIntOrUndefined(opts.limit),
},
defaultRuntime,
);
});
});
tasksCmd
.command("show")
.description("Show one background task by task id, run id, or session key")