refactor(core): migrate to Bun and converge acquisition policies and Agent boundaries
This commit is contained in:
1 parent
3f1c6d02f6
commit
27c0f27434
268 files changed
+7468
-7852
No files matched your search
@@ -1,7 +1,7 @@
|
||||
# ADR-006:生命周期事件、链式钩子与功能模块约定
|
||||
# ADR-006:生命周期事件与功能模块约定
|
||||
|
||||
- 状态:采纳(2026-10-06,用户确认方向)。
|
||||
- 关联:ADR-002(outbox 与任务引擎)、`packages/contracts/src/events.ts`(`LIFECYCLE_EVENTS`)、`packages/contracts/src/hooks.ts`(`HOOK_POINTS`)、`packages/contracts/src/features.ts`(`FEATURES`、`SLOT_POSITIONS`)、`apps/server/src/hooks/`、`apps/web/app/lib/slots.ts`。
|
||||
- 状态:采纳(2026-10-06);2026-10-12 精简通用链式钩子,保留生命周期事件和功能模块约定。
|
||||
- 关联:ADR-002(outbox 与任务引擎)、`packages/contracts/src/events.ts`(`LIFECYCLE_EVENTS`)、`packages/contracts/src/features.ts`(`FEATURES`、`SLOT_POSITIONS`)、`apps/web/app/lib/slots.ts`。
|
||||
- 参照:MoviePilot v3 `app/schemas/types.py` 的 `EventType`(广播)与 `ChainEventType`(链式),分发在 `app/runtime/events.py`。
|
||||
|
||||
## 背景
|
||||
@@ -13,23 +13,13 @@
|
||||
|
||||
## 决定
|
||||
|
||||
### 1. 两类事件
|
||||
### 1. 业务调用与事后通知
|
||||
|
||||
| | 广播事件 | 链式钩子 |
|
||||
| --- | --- | --- |
|
||||
| 对照 MP | `EventType`(transfer.complete、subscribe.added …) | `ChainEventType`(name.recognize、transfer.rename …) |
|
||||
| 时机 | 事后通知:事情已经发生并提交 | 事中:业务流程走到挂点时同步调用 |
|
||||
| 传递 | 业务事务里写 `outbox_event`,中继投递成任务(异步、至少一次) | 进程内直接调用,按注册顺序 |
|
||||
| 能做什么 | 订阅方自己建任务做后续动作 | 改写一个值(rewrite)或拒绝(intercept) |
|
||||
| 失败 | 订阅任务按引擎重试;路由函数抛错只跳过该订阅方 | 钩子抛错 / 超时只记日志、跳过该钩子,主流程照常 |
|
||||
| 去重 | outbox 幂等键(`<type>/<部分…>`),订阅任务用引擎幂等键 | 不需要 |
|
||||
同步规则由所属模块 service 明确实现,路由、能力与任务共用业务入口。跨模块需要同步取得结果时显式调用 service。
|
||||
|
||||
**何时用哪类**:只想在「X 发生之后」做点事(发通知、补字幕、预热缓存)→ 订阅广播事件;要改变正在进行的决定(换个名字、换个候选、不让整理、晚点完成)→ 注册链式钩子。拿不准就用广播:它不在主流程里,坏了也不影响主流程。
|
||||
需要在业务提交后可靠执行的动作(补字幕、通知、预热等)订阅生命周期事件。业务事务内调用 `ctx.outbox.emit(trx, …)`,与业务写入一起提交或回滚;payload 只放 ID 和少量判断事实。中继至少一次投递成任务,接收方按 ID 回查业务表并幂等执行。
|
||||
|
||||
**事务边界**:
|
||||
|
||||
- 广播事件在业务事务**里**写 outbox(`ctx.outbox.emit(trx, …)`),与业务写入一起提交或回滚;payload 只放 ID 和少量判断用的事实,订阅方按 ID 回查业务表(事件可能晚到、重复到)。
|
||||
- 钩子可能在业务事务里被调用(订阅完成判定就在行锁里):只做内存里的判断与改写,**不要发外部请求、不要写库**;缺省每个钩子 2 秒上限。需要查外部的,改成订阅广播事件后异步做。
|
||||
原先参考 MP `ChainEventType` 实现的通用 rewrite/intercept 钩子没有实际业务注册者,本轮删除其注册表、挂点与调用层。以后出现真实扩展需求时再判断抽象方式。`modules/decision-hooks.ts` 是已有待处理事项的业务处理器,语义不同,继续保留。
|
||||
|
||||
### 2. 广播事件清单
|
||||
|
||||
@@ -56,32 +46,11 @@ payload schema 都在 `EVENT_PAYLOADS`(`emit` 时校验,不合法直接抛
|
||||
|
||||
**订阅**:模块在 `outboxRoutes` 里写 `type → 路由函数`。生命周期事件可以有多个订阅方(`collectOutboxRoutes` 按模块顺序合成),其它事件(内部流程,如 `release.observed → resolveObservation`)仍只能一个模块认领。路由函数返回任务、任务数组或 `null`(不建任务);中继对第一个任务用事件的幂等键、其余用 `<键>#<任务名>`。没有订阅方的生命周期事件也会被中继认领、标记已投递,并镜像到 SSE(`worker/index.ts` 的镜像前缀)。
|
||||
|
||||
### 3. 链式钩子清单
|
||||
|
||||
挂点名称与语义在 `HOOK_POINTS`,入参 / 返回值类型在 `apps/server/src/hooks/points.ts`。
|
||||
|
||||
| 挂点 | 类型 | 位置 | 能做什么 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name.recognize` | rewrite | `resolveRelease`(标题解析后、召回前)、`identifyParsed`(整理 / 接管按名称识别) | 改写解析结果(标题、年份、季集、外部 ID) |
|
||||
| `organize.rename` | rewrite | `planImport`,模板命名生成目标之后(保留原名 / 原地入库不调) | 改写主视频目标文件名;同主干的附属文件跟着改;结果带路径分隔符或空名的钩子跳过;改名后撞名转 `duplicate_target` |
|
||||
| `organize.intercept` | intercept | `planImport`,写整理记录之前(预演也调) | 返回原因即拒绝:记录为失败,`reason = intercepted:<原因>` |
|
||||
| `resource.selection` | rewrite | 订阅 / 收录版本重评(`reevaluate`)择优排序之后 | 重排或过滤候选;不认识的 ID 忽略,不能加入新候选 |
|
||||
| `subscription.completion.check` | intercept | `completeSubscription`,标记完成之前 | 追加完成条件:返回原因即这次不完成,订阅继续追踪 |
|
||||
|
||||
语义:
|
||||
|
||||
- 注册在模块启动时完成:`ServerModule.registerHooks(hooks, ctx)`,`bootstrap` 按 `SERVER_MODULES` 顺序调用;执行顺序即注册顺序(不设优先级数字,需要先后就调整模块顺序)。
|
||||
- rewrite:每个钩子拿到上一个钩子的结果,返回新值或 `undefined`(不改)。intercept:第一个返回非空原因的钩子拒绝,后面不再执行。
|
||||
- 每个钩子单独限时(缺省 2 秒,可在注册时改),超时或抛错记 warn 日志、跳过这个钩子;拦截类出错按放行处理。
|
||||
- 没有注册时调用方直接用原值(`hasHook` 为 false,不构造上下文)。
|
||||
|
||||
MP 的其它链式事件(`transfer.overwrite.check`、`resource.download`、`discover.source`、`recommend.source` 等)等有真实需求再加:在 `HOOK_POINTS` 登记、在 `points.ts` 写类型、在业务节点调用。
|
||||
|
||||
### 4. 功能模块约定
|
||||
### 3. 功能模块约定
|
||||
|
||||
一个外围功能 = 服务端一个 `ServerModule` + contracts 里一份 manifest + (可选)前端插槽注册:
|
||||
|
||||
- **manifest**(`FEATURES`):名称、说明、怎么用、订阅的事件、提供的能力、页面入口、插槽位置、钩子。服务端模块 `manifest: featureManifest("<id>")`;`modules.test.ts` 核对模块与清单一一对应、订阅的事件与 `outboxRoutes` 一致。
|
||||
- **manifest**(`FEATURES`):名称、说明、怎么用、订阅的事件、提供的能力、页面入口、插槽位置。服务端模块 `manifest: featureManifest("<id>")`;`modules.test.ts` 核对模块与清单一一对应、订阅的事件与 `outboxRoutes` 一致。
|
||||
- **功能清单只给智能体**:系统提示「能力地图」一节末尾列出外围功能(`renderFeatures`),能力只列本次可用的。**不做开关**:功能都直接启用。
|
||||
- **前端插槽**(`apps/web/app/lib/slots.ts`):位置见 `SLOT_POSITIONS`(侧栏入口、作品页操作区、作品页面板区、整理记录行操作、设置分区)。动作类位置注册 `{ label, icon, visible?, Dialog }`,页面渲染成按钮 / 菜单项、点了打开对话框;面板类位置注册组件,挂载点按顺序渲染。注册写在 `app/features/<功能>.tsx`,`app/features/index.ts` 汇总。**默认直接显示,不做开关**。已挂载:作品页操作区、作品页面板区(`library.$id.tsx`)、整理记录行菜单(`organize.tsx`);侧栏与设置分区先只定义位置。
|
||||
- 样板:补字幕(`modules/subtitles`)——manifest、订阅 `organization.finished`(认出值得补字幕的整理,自动补字幕还没做,只记调试日志不建任务)、写入后发 `subtitle.written`、前端在作品页操作区和整理记录行菜单挂「补字幕」。
|
||||
@@ -89,5 +58,4 @@ MP 的其它链式事件(`transfer.overwrite.check`、`resource.download`、`d
|
||||
## 不做
|
||||
|
||||
- 插件系统、热加载、插件市场、功能开关。
|
||||
- 钩子优先级数字、钩子里做外部请求。
|
||||
- 把广播事件放进内存队列(MP 做法):我们已有 outbox,事件要能跨进程、跨重启。
|
||||
@@ -0,0 +1,15 @@
|
||||
# ADR-007: Bun 1.4.3 与 Linux 解析沙箱
|
||||
|
||||
状态:采纳。取代 ADR-004 的运行时、依赖安装与编译方式;更新 ADR-005 的权限隔离实现。
|
||||
|
||||
后端、前端开发工具及 Vitest 使用 Bun 1.4.3。根 package.json 管理 workspace 与统一依赖,bun.lock 锁定解析结果。.bun-version、packageManager 与 Docker 构建镜像固定同一版本。现有业务依赖版本保持不变。
|
||||
|
||||
Deno 全局调用改为 Node API 或 @umt/runtime 中的 Bun HTTP、WebSocket、子进程与流适配。HTTP 出口使用 proxy:false 强制直连,显式代理使用 respectNoProxy:false;环境代理由现有出口选择函数处理。
|
||||
|
||||
Bun 不提供 Deno 权限撤销。解析沙箱采用一次性子进程,仅传入 PATH 环境变量,预加载内置解析器与 WASM、接收 stdin 后才安装 Linux seccomp 白名单。策略通过 Node-API 模块实施,启用 no_new_privs 与 TSYNC,覆盖所有线程。默认 EPERM 拒绝未列出的系统调用:文件路径访问、socket/connect、exec、fork、ptrace、mount、io_uring 均不允许。clone 只允许 CLONE_THREAD,clone3 返回 ENOSYS 供 libc 使用受检的 clone。策略安装后直接核验 socket/openat/execve 被内核以 EPERM 拒绝。已有 stdin/stdout 与运行时内存管理调用允许使用。不同系统调用架构直接终止。
|
||||
|
||||
这不是完整虚拟机:内核与 Bun 内存安全仍是信任边界。环境变量不再通过运行时权限隐藏,改为父进程清空环境;输入只送到固定任务表,不执行上传代码。父进程继续实施超时与 RSS 上限并 SIGKILL。建立隔离失败直接拒绝,不回退到主进程执行。策略目前支持 Linux x64/arm64;其它系统不能执行解析沙箱任务,可使用 Linux Docker 开发。
|
||||
|
||||
编译脚本静态嵌入所有数据库迁移与 WASM,关闭可执行文件自动加载 .env/bunfig。生产部署同时携带 sandbox-policy.node 与内置 skills 目录。C 编译器及 Node-API 头文件仅在构建阶段需要。check:binary 在空环境、/tmp 工作目录验证模块图、原生策略及 WASM 可加载。
|
||||
|
||||
文件代理与解析代理共用 `@umt/runtime/worker` 的逐行 JSON 协议、消息大小限制、异常退出与有界回收。仍为一次性进程,不在通信层自动重试;文件进度停滞、SIGTERM 清理宽限与解析总时长/RSS/SIGKILL 策略由各自代理维护。服务端 FS_PROXY_MODE 缺省 subprocess,只有显式 auto 才允许进程内回退。开发指南记录抽象边界与后续提取常驻管理的条件。
|
||||
Reference in new issue
Block a user