AGENTS.md@src/host/service · git:20260922.109483f · 2026-09-22 · sha256 9a17b19df76f4cef
AGENTS.md@src/host/service git:20260922.109483fA
Immutable. This exact content never changes and is served at /api/v1/blob/9a17b19df76f4cef.
# Service Module Rules ## 适用范围 适用于 `src/host/service/`。本文件定义模式二 API Service 模块特有的稳定治理边界与修改规则;通用规范遵循根 `AGENTS.md`,测试规范遵循 `tests/AGENTS.md`。 本文件描述治理原则,不是源码结构说明。文件移动、重命名或内部重构后,只要职责、事实源、依赖方向与对外契约未变,本文件不随之修改。 ## 核心职责 让一个工作流以**常驻 HTTP 服务**的形态对外提供能力,并管理它的完整生命周期: - 服务子进程的生命周期:启动、停止、崩溃判定、随主进程重启的自动恢复; - 端口分配与子进程的组合挂载产物(服务进程如何被装配起来); - 服务进程内的对外协议:请求解析、鉴权、并发上限、流式响应、错误映射; - 多用户隔离:外部用户标识到会话的稳定映射与持久化。 判断归属:该修改是否改变**服务进程的生命周期、进程间协作契约、对外协议或用户隔离语义**?若主要属于工作流语义、编排推进、图判定、界面展示,应放到对应模块。 ## 非职责 - 不实现编排运行时与 Agent 执行——服务进程只是复用宿主能力; - 不定义工作流文档的形状与合法性(归共享契约与图模块); - 不拥有持久化原语,也不拥有服务文档以外的资源文件语义(归存储与各模块自身); - 不实现 GUI 端点(属 remote),不渲染界面(属 client); - 不直接读写其他模块的内部状态。 ## 进程边界 本模块横跨**两个进程侧**,两侧的规则不同: - 主进程侧:子进程生命周期、端口分配、挂载产物渲染; - 服务进程侧:对外协议、会话隔离、编排调用。 两侧只允许通过**磁盘事实**(服务文档、挂载产物文件)与**进程启动参数/标准流**协作: - 禁止依赖内存共享、模块级单例或跨进程函数调用; - 服务进程侧不得反向操作主进程侧的状态(例如"自己把自己拉起"会造成子进程无限复制); - 两个进程侧各自可独立测试:主进程侧的进程创建必须经可注入的子进程工厂,服务进程侧的编排依赖必须经抽象缝注入。 ## 生命周期契约 - 服务状态的事实源是**持久化的服务文档**,内存登记只是存活子进程的快速索引;两者不一致时以文档为准并自愈; - 启动幂等:同一服务在存活或启动中时再次启动必须被拒绝(明确错误码),不得产生第二个子进程、不得重复分配端口、不得双写挂载产物; - 停止必须收敛:先请求优雅退出,超过宽限期后强制终止**整棵进程树**(只杀壳进程会留下孤儿并占用端口); - 非主动退出(崩溃)必须被判定并记录,且不得覆盖"已主动停止"的终态; - 随主进程重启的自动恢复只针对"文档状态为运行中"的服务,且必须容忍端口被占用(重新分配),单个恢复失败不得阻断其余; - 主进程卸载时必须尽力释放全部子进程与其定时器;重复调用清理必须安全。 ## 并发与幂等 - 启动的互斥必须在**第一个异步点之前**同步登记,否则并发的两次启动会同时通过检查并各自创建子进程(孤儿进程 + 端口泄漏); - 停止可被重复调用:重复调用不得泄漏或重复排程强杀定时器(宽限期以最后一次请求为准); - 关闭/退出事件与主动停止存在竞态:清理必须幂等(内存条目、定时器、文档状态各自只处理一次); - 会话映射的并发首解析必须去重(同一用户标识只产生一个会话),且"读改写"必须落在同一临界区内,不得跨锁执行。 ## 资源释放 - 子进程的 stdout/stderr 必须持续消费,否则管道写满会阻塞子进程;转发失败(控制台不可用)必须降级而非放大; - 强杀定时器必须在进程退出、条目注销、模块卸载三条路径上都被清理; - 端口探测必须及时释放监听句柄,不得让探测本身占用端口; - 任何后台等待都必须有上界:请求等待超时后必须**停止后台编排运行**并给出明确错误;客户端断开必须传播为取消而不是继续占用并发槽。 ## 外部依赖显式 - 外部可执行文件(子进程命令)、网络服务(webServer)、官方能力(会话/Agent 创建)一律经运行时守卫 + 可注入缝使用,缺失时给出明确错误或降级告警,不得静默失效; - 禁止在没有明确职责的位置读取环境变量或全局进程状态;进程环境继承、工作目录等启动参数必须集中在一处构造,便于审计; - 对外协议层(HTTP)只做"请求/响应 ⇄ 核心调用"的转换与错误映射,**不得包含业务判断**;核心逻辑必须可在无 HTTP 的情况下测试。 ## 单一事实源 - 服务状态字段只有一处写者;运行字段的更新必须"读最新值后合并写",不得整文档覆盖(并发写入会互相丢更新); - 每个外部用户标识到会话的映射只有一处实现,且持久化文件按服务隔离; - 服务进程的挂载产物只有一处渲染实现,产物是"配置事实"而非"可手改文件"; - 一个概念只允许一处字面量:错误码、状态取值、端点路径的取值来源必须唯一。 ## 公共边界 - 模块外只允许经模块的**公共入口**导入,禁止直接引用模块内部文件; - 只把真实、稳定、可维护的契约放进公共入口(生命周期管理器与其依赖缝、端口分配、挂载产物渲染、会话映射、协议核心与 HTTP 注册); - 内部实现(进程树终止、标识消毒、密钥哈希、请求体读取、响应写出、会话事件读取助手)不进入公共入口;禁止「全量 barrel」; - 新增公共符号前先判断它是否表达对外稳定契约;放宽入口比收紧入口的代价更高。 ## 依赖边界 - 只允许依赖共享契约层、存储门面、编排运行时的**接口**与图判定; - 不得依赖 agent / tools / remote / client 的实现; - 服务进程入口插件属包级二级入口(对外发布路径固定),其进程内实现属本模块;入口只做装配与协调,不承载业务语义; - 依赖方向单向:调用方依赖本模块的生命周期与协议能力,本模块不反向依赖任何调用方。 ## 契约稳定性 - 错误码、服务状态取值、对外协议(端点路径、请求/响应字段、鉴权方式)属公共契约:修改必须同步调用方、测试与文档; - 服务的持久化字段与映射文件形态属兼容契约:新增字段默认可选;退役字段只保留读取兼容并说明剥除时机; - 挂载产物是子进程的装配契约:其字段增删必须同时检查渲染方与消费方的解析;产物变更属行为变化,必须记录旧行为 → 新行为 → 原因 → 影响范围 → 测试变化。 ## 模块内部组织 - 文件结构不是架构契约,禁止为"看起来模块化"机械拆分;拆分依据只能是职责、变化原因、进程边界与测试边界; - 文件命名必须表达**稳定职责与所属维度**;同名概念(尤其是跨进程侧的"会话"相关概念)必须能一眼区分职责,名称失真的文件应立即重命名并同步全部引用; - 不得创建 `utils.ts`、`helpers.ts`、`common.ts`、`shared.ts`、`misc.ts` 等无明确职责的聚合文件; - 内部文件之间使用相对路径导入,公共入口不含实现。 ## 修改前检查 1. 改动属于主进程侧还是服务进程侧?两侧的契约是否都要同步? 2. 是否改变服务生命周期(启动/停止/崩溃/恢复)或错误码?受影响的调用方与测试是哪些? 3. 是否引入新的并发窗口(跨 await 的检查-再动作)?互斥登记是否在第一个异步点之前? 4. 是否新增未释放的资源(定时器、监听器、子进程、管道)? 5. 是否把业务判断放进协议层,或把进程装配细节放进核心逻辑? 6. 是否产生第二份事实源(重复的状态写入、重复的校验、重复的映射)? 7. 是否越界进入其他模块职责(工作流语义、GUI 端点、界面)? ## 验证 除根 `AGENTS.md` 的标准命令外,本模块改动必须额外确认: - 并发启动、重复停止、崩溃/退出竞态、自动恢复失败等路径有测试锁定(用可注入的子进程与端口工厂,不真实 fork); - 生命周期的状态迁移(含"文档状态与内存登记不一致")有测试锁定; - 协议层的参数错误、鉴权失败、并发超限、超时、客户端断开均有测试锁定,且断言的是可观察的响应与调用副作用; - 目录与文件路径的**不真实执行**保证:测试不得真实运行子进程命令、不得真实强杀进程; - 公共入口导出面未意外扩大、无旧路径残留、无循环依赖、无重复实现。 ## 核心原则 1. 两个进程侧只经磁盘事实与启动参数协作,不共享内存。 2. 服务状态以持久化文档为事实源,内存登记只是索引。 3. 启动互斥在第一个异步点之前,停止必须收敛整棵进程树。 4. 一切后台等待有上界,取消(超时/断开)必须传播。 5. 协议层只做转换与错误映射,核心逻辑可脱离 HTTP 测试。 6. 模块外只经公共入口协作,内部实现不外溢。 7. 生命周期与协议契约变化必须显式记录并锁定测试。