fix(#438): 显式启动函数 startHub() — import 不再绑端口,并清零 8 个历史失败 - #475
Conversation
…change) 方案 C step 1/2(通信龙裁定):hub 实现整体平移到 server.ts, 下一个 commit 引入 3 行 run-entry shim index.ts 与显式启动函数。 纯 rename 独立成 commit 以保留 R100 历史。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
通信龙裁定方案 C:职责拆分代替 57 处调用点改写。
server.ts(原 index.ts,上一 commit 纯 rename):
- 删除模块级 export const server = bootServer() 副作用;import 本模块
不再绑任何端口、不装 sweeper、不装信号处理
- 新增 export startHub(opts?):bootServer + retention/stale sweeper +
SIGTERM/SIGINT 处理 + banner(banner 改报实际绑定地址,port:0 下正确)
- startHub 单发可观测:二次调用显式抛错并带首启坐标(时间/host:port),
不做静默 no-op;getStartedHub() 只读内省
- sweeper 定时器 unref:进程存活由 Bun.serve socket 决定,测试/未来调用
方 stop 掉 server 后不会被够不着的 interval 吊死
- vault 启动横幅改 fire-and-forget(原为顶层 await,函数内不可用)
index.ts(新,12 行 run-entry shim):
- import { startHub } from "./server.js"; startHub();
- `bun run src/index.ts` 行为完全不变 → tests/ 下 51 个直跑脚本、
package.json start/dev、docs 脚本零改动
- 不使用 import.meta.main / argv 探测类入口魔法
bin/commhub.ts:argv 解析后显式 import server.js 并调用 startHub()。
测试迁移与修复:
- uploads-http / api-host-supervisors-fallback:迁移到
bootServer({port:0}) 私有实例 + 读回实际端口,afterAll stop
- api-host-supervisors:seed admin 显式提权("首个注册用户自动 admin"
在 aggregate 共享 DB 下不成立,paths 7/8 曾静默测错角色)
- response-charset:源码片段断言从 index.ts 改读 server.ts
- 新增 boot-side-effect.test.ts:import server.js 不绑端口(探测端口
bind 必须成功)+ startHub 单发/二次抛错/坐标可见/bootServer 仍可用
- observer-avatar-http:清理已失效的 PORT 逻辑
验收证据:
- witnessed-red:pre-corrective HEAD 7fa3ad3 上 import 后探测端口
EADDRINUSE;本候选上同探测 PORT FREE
- 全量 bun test src/:557 pass / 0 fail ×2 次复现(基线 8 fail 清零,
含历史 #434 aggregate 债)
- 门禁双向:uploads-http + observer-avatar-http 正反序均 22/0
- 直跑等价:bun run src/index.ts → curl /health 200
- 打包验收:npm pack → 干净目录安装 → 真跑 node_modules/.bin/
commhub-server → curl /health 200、新路由 401 如预期
- CI 层级核查:两个迁移文件不在 L0/L1 清单,仅全量 bun test 覆盖;
boot-side-effect 依赖 MCP SDK,不登记 L0
package.json main 仍指 src/index.ts(行为与已发布版本一致:库 import
即启动)。是否切到 server.ts(库 import 无副作用)留待审查裁定。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 103e52ce81
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return true; | ||
| } | ||
| // Cleanup stale entries every 5 minutes | ||
| setInterval(() => { |
There was a problem hiding this comment.
Start background intervals only with the hub lifecycle
When tooling imports server.js without calling startHub(), or a caller stops a private bootServer() instance, this referenced interval—and the task-expiration patrol at line 504—continues running. Consequently, the newly advertised side-effect-free import can keep the process alive indefinitely and the patrol can mutate the database even though no hub is running; the unref() calls in startHub() only cover the two sweepers created there. Move these module-level intervals into the explicit startup lifecycle or unref and expose cleanup for them.
Useful? React with 👍 / 👎.
|
|
||
| const PROBE_PORT = 18500 + Math.floor(Math.random() * 500); | ||
|
|
||
| describe("#438 corrective — importing server.ts is side-effect-free", () => { |
There was a problem hiding this comment.
Add the required Docker harness for this test suite
This adds a standalone regression suite without an independent Dockerfile/run script or a persisted docs/tests/report-testN.txt result, so the repository's required Docker-only, recorded test gate cannot execute and retain evidence for the new startup behavior. Add the corresponding isolated Docker test suite and saved report rather than relying solely on direct bun test execution.
AGENTS.md reference: AGENTS.md:L7-L9
Useful? React with 👍 / 👎.
独立验收(通信测试马):PASS四件套绿门 PASS( 措辞更正(验收方 findings,已核实属实)本 PR 的不变量应表述为「import 不绑端口」,而不是「import 零副作用」。 另:当前干净退出依赖 两条均为既有状况、非本 PR 引入,已开 #476 归档并认领,本 PR 不顺手扩张范围。 (合并前尝试编辑 PR body 未生效——GitHub Projects classic 弃用导致的 GraphQL 报错;同样内容已写入 squash 合并提交信息。) |
#438 corrective(方案 C)。已通过通信龙独立复审——下列数字与行为均为复审者亲自重跑,非采信作者自述。
背景:为什么不能用
import.meta.main#438 原方案把生产启动块放进
if (import.meta.main)。但已发布包的进程入口不是src/index.ts:package.json的bin指向bin/commhub.ts,它通过import("../src/index.js")动态加载,因此那里的import.meta.main恒为false→ 进程起来但不监听任何端口。本地bun run src/index.ts永远绿,只有装了发布包才会露馅。本 PR 明确不使用
import.meta.main,也不使用argv[1]探测这类等价魔法。方案 C:把「被 import」和「被直接运行」拆成两个文件
关键不对称:
要消除副作用的是被 import 的那条路;被直接运行本来就该有副作用。两个角色原本挤在同一文件,拆开即可,无需改动 57 个调用点。
d4dcbee0— 纯 renamesrc/index.ts→src/server.ts(git R100,0 增 0 删,便于复审区分"移动"与"真改")103e52ce— 全部实际改动,8 文件 +232/-80(对比方案 A 的 51+ 文件)新结构:
server.ts导出startHub()(含 sweepers / signal / banner,单发可观测:二次调用显式抛错并带首启坐标);index.ts是 12 行 run-entry shim;bin/commhub.ts解析 argv 后显式调用。复审实测证据
核心不变量(witnessed-red / witnessed-green,两向都跑)
51 个直跑脚本不受影响
发布路径
全量回归:历史 8 个失败一并清零
顺带修掉了
api-hostpaths 7/8 的「首个用户即 admin」aggregate 耦合(改为显式提权)。这 8 个长期红是基线噪音,会掩盖真实回归,清零后 aggregate 首次成为可信门。新增
boot-side-effect.test.ts作为常驻回归门,防止副作用被重新引入。已知与决策
package.json的main仍指向src/index.ts(run-entry shim),因此库 import 本包仍会启动服务,与已发布版本行为一致。如需库 import 无副作用,将main切至src/server.ts是独立的 semver 可见决策,需另开单评估——不在本 PR 顺手做。sweeper定时器unref()的空转风险已实测排除:把 stale sweep 调到 5 秒使定时器在观察窗内触发 14+ 次,进程空闲存活 73 秒,两端curl /health均 200 ——Bun.serve的 socket 独立撑住事件循环,不存在「起来即退」。