modules/{auth-settings,sources-identity,acquisition-organize,agent} placeholders listed in
SERVER_MODULES; ServerModule registers routes (under /api/v1, authenticated), tasks,
outbox routes, capabilities and background loops. bootstrap() builds ServerContext (db,
config, logger, events, engine, outbox writer, secrets placeholder, settings reader,
capability registry), fills unregistered tasks with stubs and rejects duplicate task or
outbox ownership. worker starts the OpenWorkflow worker, OutboxRelay and module start().
--role migrate also migrates the engine schema; api/worker connect without migrating.
tasks: TaskRegistry.has, migrateEngineSchema.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@umt/server
服务进程(Deno):Hono HTTP API(前缀 /api/v1,OpenAPI 见 /api/openapi.json、/api/docs)、SSE 事件流与 worker 入口。--role api|worker|all 决定本进程承担哪部分,默认 all 单进程运行。业务规则不写在这里,只做编排、鉴权、审计与协议适配。
模块骨架(W2)
src/
main.ts 进程入口:按 --role 装配
context.ts bootstrap():ServerContext、任务注册表(模块 + 空壳兜底)、outbox 路由合并、连接引擎
http/app.ts createApp():系统路由 + 逐个挂 SERVER_MODULES 的 registerRoutes
worker/index.ts startWorker():引擎 worker + OutboxRelay + 各模块 start()
modules/
types.ts ServerModule / ServerContext / OutboxWriter / SecretStore / SettingsReader(共享,不随子任务改)
index.ts SERVER_MODULES 清单(四个模块已登记,子任务不需要再改)
auth-settings/ W2-A(provided.ts 提供 secrets / settings 实现)
sources-identity/ W2-B
acquisition-organize/ W2-C
agent/ W2-D(capability-registry.ts 负责 authorize / audit 注入)
分工规则:各 W2 子任务只改自己的 modules/<area>/ 目录,以及 packages/contracts、packages/db 中本域的文件(新迁移按编号顺延,开工前在群里认领编号)。modules/types.ts、modules/index.ts、context.ts、http/app.ts、worker/index.ts、main.ts 是共享装配,确需改动时单独提交并通知其他子任务;http/auth.ts、http/sse.ts 归 W2-A。跨模块只通过 DB 表 + outbox_event(事件类型在 contracts/events.ts)+ contracts 类型耦合,不互相 import 对方模块。
一个模块长这样(modules/<area>/index.ts 导出,目录内自由拆 routes.ts / service.ts / handlers/*.ts):
import { createRoute } from "@hono/zod-openapi";
import { capabilitySpec, EventType } from "@umt/contracts";
import { defineTask } from "@umt/tasks";
import { requireScope } from "../../http/auth.ts";
import type { ServerModule } from "../types.ts";
export const sourcesIdentityModule: ServerModule = {
name: "sources-identity",
// 路径相对 /api/v1,已过 API Key 鉴权;每个路由自己声明 scope
registerRoutes(app, ctx) {
app.use("/sites/*", requireScope("read"));
app.openapi(createRoute({ method: "get", path: "/sites", /* … */ }), async (c) => c.json(/* … */));
},
// 同名任务只能由一个模块注册;未注册的 §3.1-8 任务由 @umt/tasks 空壳兜底
registerTasks(registry, ctx) {
registry.register(defineTask({ name: "resolveObservation", version: 1, /* … */ handler: async (t) => {
await ctx.db.transaction().execute(async (trx) => {
// …写业务表,同事务发事件(payload 按 contracts 校验)
await ctx.outbox.emit(trx, EventType.releaseResolved, payload, { idempotencyKey: `release.resolved:${id}` });
});
} }));
},
// 消费别的模块发出的事件:type → 任务;同一 type 只能一个模块认领
outboxRoutes: () => ({ "release.resolved": (ev) => ({ task: "evaluateAcquisition", input: { sourceReleaseId: (ev.payload as { sourceReleaseId: string }).sourceReleaseId } }) }),
// 能力:规格一律取 contracts,处理器调本模块服务
registerCapabilities(registry, ctx) {
registry.register(capabilitySpec("release.search"), async (input, c) => ({ items: [], coverage: [] }));
},
// worker 角色的后台循环(qB 轮询、Feed 调度…);引擎与中继已启动
async start(ctx) {},
async stop() {},
};
ServerContext 字段:db(Kysely)、config、logger(pino,自动脱敏)、events(SSE 总线 EventBusPort)、engine(TaskEngine:enqueue/signal/cancel/get/list;register* 阶段不要调用)、outbox(emit(trx, type, payload, {idempotencyKey}) + worker 的 relay)、secrets(SecretStore,W2-A 实现前调用会抛错)、settings(读 settings 表并合并缺省)、capabilities(CapabilityRegistry)。domain ↔ contracts 的取值转换一律用 @umt/mapping。
角色:all 单进程时连接引擎会顺带跑引擎迁移;api / worker 分进程部署时由 --role migrate 预先迁移(db + openworkflow),两个进程都以 runMigrations:false 连接。