# Service Module Rules

## 适用范围

适用于 `src/host/service/`。本文件定义模式二 API Service 模块特有的稳定治理边界与修改规则；通用规范遵循根 `AGENTS.md`，测试规范遵循 `tests/AGENTS.md`。

本文件描述治理原则，不是源码结构说明。文件移动、重命名或内部重构后，只要职责、事实源、依赖方向与对外契约未变，本文件不随之修改。

## 核心职责

让一个工作流以**常驻 HTTP 服务**的形态对外提供能力，并管理它的完整生命周期：

- 服务子进程的生命周期：启动、停止、崩溃判定、随主进程重启的自动恢复；
- 端口分配与子进程的组合挂载产物（服务进程如何被装配起来）；
- 服务进程内的对外协议：请求解析、鉴权、并发上限、流式响应、错误映射；
- 多用户隔离：外部用户标识到会话的稳定映射与持久化。

判断归属：该修改是否改变**服务进程的生命周期、进程间协作契约、对外协议或用户隔离语义**？若主要属于工作流语义、编排推进、图判定、界面展示，应放到对应模块。

## 非职责

- 不实现编排运行时与 Agent 执行——服务进程只是复用宿主能力；
- 不定义工作流文档的形状与合法性（归共享契约与图模块）；
- 不拥有持久化原语，也不拥有服务文档以外的资源文件语义（归存储与各模块自身）；
- 不实现 GUI 端点（属 api），不渲染界面（属 client）；
- 不直接读写其他模块的内部状态。

## 进程边界

本模块横跨**两个进程侧**，两侧的规则不同：

- 主进程侧：子进程生命周期、端口分配、挂载产物渲染；
- 服务进程侧：对外协议、会话隔离、编排调用。

两侧只允许通过**磁盘事实**（服务文档、挂载产物文件）与**进程启动参数/标准流**协作：

- 禁止依赖内存共享、模块级单例或跨进程函数调用；
- 服务进程侧不得反向操作主进程侧的状态（例如"自己把自己拉起"会造成子进程无限复制）；
- 两个进程侧各自可独立测试：主进程侧的进程创建必须经可注入的子进程工厂，服务进程侧的编排依赖必须经抽象缝注入。

## 生命周期契约

- 服务状态的事实源是**持久化的服务文档**，内存登记只是存活子进程的快速索引；两者不一致时以文档为准并自愈；
- 启动幂等：同一服务在存活或启动中时再次启动必须被拒绝（明确错误码），不得产生第二个子进程、不得重复分配端口、不得双写挂载产物；
- 停止必须收敛：先请求优雅退出，超过宽限期后强制终止**整棵进程树**（只杀壳进程会留下孤儿并占用端口）；
- 非主动退出（崩溃）必须被判定并记录，且不得覆盖"已主动停止"的终态；
- 随主进程重启的自动恢复只针对"文档状态为运行中"的服务，且必须容忍端口被占用（重新分配），单个恢复失败不得阻断其余；
- 主进程卸载时必须尽力释放全部子进程与其定时器；重复调用清理必须安全。

## 并发与幂等

- 启动的互斥必须在**第一个异步点之前**同步登记，否则并发的两次启动会同时通过检查并各自创建子进程（孤儿进程 + 端口泄漏）；
- 停止可被重复调用：重复调用不得泄漏或重复排程强杀定时器（宽限期以最后一次请求为准）；
- 关闭/退出事件与主动停止存在竞态：清理必须幂等（内存条目、定时器、文档状态各自只处理一次）；
- 会话映射的并发首解析必须去重（同一用户标识只产生一个会话），且"读改写"必须落在同一临界区内，不得跨锁执行。

## 资源释放

- 子进程的 stdout/stderr 必须持续消费，否则管道写满会阻塞子进程；转发失败（控制台不可用）必须降级而非放大；
- 强杀定时器必须在进程退出、条目注销、模块卸载三条路径上都被清理；
- 端口探测必须及时释放监听句柄，不得让探测本身占用端口；
- 任何后台等待都必须有上界：请求等待超时后必须**停止后台编排运行**并给出明确错误；客户端断开必须传播为取消而不是继续占用并发槽。

## 外部依赖显式

- 外部可执行文件（子进程命令）、网络服务（webServer）、官方能力（会话/Agent 创建）一律经运行时守卫 + 可注入缝使用，缺失时给出明确错误或降级告警，不得静默失效；
- 禁止在没有明确职责的位置读取环境变量或全局进程状态；进程环境继承、工作目录等启动参数必须集中在一处构造，便于审计；
- 对外协议层（HTTP）只做"请求/响应 ⇄ 核心调用"的转换与错误映射，**不得包含业务判断**；核心逻辑必须可在无 HTTP 的情况下测试。

## 单一事实源

- 服务状态字段只有一处写者；运行字段的更新必须"读最新值后合并写"，不得整文档覆盖（并发写入会互相丢更新）；
- 每个外部用户标识到会话的映射只有一处实现，且持久化文件按服务隔离；
- 服务进程的挂载产物只有一处渲染实现，产物是"配置事实"而非"可手改文件"；
- 一个概念只允许一处字面量：错误码、状态取值、端点路径的取值来源必须唯一。

## 公共边界

- 模块外只允许经模块的**公共入口**导入，禁止直接引用模块内部文件；
- 只把真实、稳定、可维护的契约放进公共入口（生命周期管理器与其依赖缝、端口分配、挂载产物渲染、会话映射、协议核心与 HTTP 注册）；
- 内部实现（进程树终止、标识消毒、密钥哈希、请求体读取、响应写出、会话事件读取助手）不进入公共入口；禁止「全量 barrel」；
- 新增公共符号前先判断它是否表达对外稳定契约；放宽入口比收紧入口的代价更高。

## 依赖边界

- 只允许依赖共享契约层、存储门面、编排运行时的**接口**与图判定；
- 不得依赖 agent / tools / api / 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. 生命周期与协议契约变化必须显式记录并锁定测试。
