feat(db): 0013 tasks tables and 0014 agent_run_event; tasks uses db migrations

tasks_run_meta/tasks_fact/tasks_op_ledger move into packages/db (if not exists, old
tasks_migration tables dropped); packages/tasks/migrations and migrateTasksTables
removed, tests migrate their schema with @umt/db. agent_run_event(run_id, seq, type,
at, payload) with (run_id, seq) primary key; types regenerated, db-sync covers it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 19:15:50 +08:00
1 parent db5352eecd
commit da8e53bbca
13 files changed
+250 -36

No files matched your search

@@ -0,0 +1,60 @@
import type { Kysely } from "kysely";
import { dropTables, execDdl, oneOf } from "../src/migration-helpers.ts";
// @umt/tasks 的三张自有表并入统一迁移(W1 审查 S2;原 packages/tasks/migrations/0001_tasks_tables)。
// 表结构与原迁移一致,tasks 包通过注入的 Kysely 访问:
// - tasks_run_meta:引擎 run 的业务侧元数据(traceId/优先级/取消标志),取消标志是 abortSignal 的来源
// - tasks_fact:signal 之前先落库的事实;waitForFact 以它为准,信号只用来加速唤醒
// - tasks_op_ledger:操作账本(fencing),记录 attempt_token,旧 attempt 的提交会被拒绝
// 用 if not exists:W1 期间用 migrateTasksTables 建过表的库可以直接接管;顺手删掉旧的独立迁移记录表。
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
export async function up(db: Kysely<any>): Promise<void> {
await execDdl(
db,
`
create table if not exists tasks_run_meta (
run_id text primary key,
task_name text not null,
task_version integer not null,
idempotency_key text not null,
trace_id text,
priority integer not null default 0,
cancel_requested_at timestamptz,
cancel_reason text,
created_at timestamptz not null default now()
);
create index if not exists tasks_run_meta_task_idx on tasks_run_meta (task_name, created_at);
create table if not exists tasks_fact (
id bigserial primary key,
run_id text not null,
name text not null,
payload jsonb,
created_at timestamptz not null default now()
);
create index if not exists tasks_fact_run_name_idx on tasks_fact (run_id, name, id);
create table if not exists tasks_op_ledger (
op_key text primary key,
run_id text,
attempt_token uuid not null,
attempts integer not null default 1,
status text not null ${oneOf("status", ["started", "succeeded"])},
result jsonb,
last_error text,
started_at timestamptz not null default now(),
completed_at timestamptz
);
do $$ begin
execute format('drop table if exists %I.tasks_migration, %I.tasks_migration_lock', current_schema(), current_schema());
end $$;
`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: 同上
export async function down(db: Kysely<any>): Promise<void> {
await execDdl(db, dropTables(["tasks_op_ledger", "tasks_fact", "tasks_run_meta"]));
}
@@ -0,0 +1,40 @@
import type { Kysely } from "kysely";
import { dropTables, execDdl, oneOf } from "../src/migration-helpers.ts";
// Agent 运行事件持久化(W1 审查 B9;REQ §10.6):先落库再推 SSE,断线/跨进程按 (run_id, seq) 回放。
// type 取值与 contracts AgentRunEventType 一致;payload = 事件主体去掉 type(contracts agentRunEventToRow)。
const TYPES = [
"run_started",
"text_delta",
"thinking_delta",
"tool_call_start",
"tool_call_end",
"tool_call_rejected",
"compaction",
"run_completed",
"run_failed",
];
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
export async function up(db: Kysely<any>): Promise<void> {
await execDdl(
db,
`
create table agent_run_event (
run_id uuid not null references agent_run (id) on delete cascade,
seq bigint not null check (seq >= 0),
type text not null ${oneOf("type", TYPES)},
at timestamptz not null default now(),
payload jsonb not null default '{}'::jsonb,
constraint agent_run_event_pkey primary key (run_id, seq)
);
create index agent_run_event_at_idx on agent_run_event (at);
`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: 同上
export async function down(db: Kysely<any>): Promise<void> {
await execDdl(db, dropTables(["agent_run_event"]));
}