Skip to content

非法 OS_TENANCY_POSTURE 的「拒绝启动」实际发生在端口已监听之后 —— 第一次抛错被 serve.ts 的 AuthPlugin 宽 catch 吞掉 #5359

Closed

Description

@os-zhuang

发现于 #5269(content/docs/deployment/tenancy-modes.mdx 文档核证)过程中,逐断言对码时撞到;本单只记录,不在该文档 PR 里改。

声称的契约

packages/types/src/env.ts:145-160 —— resolveTenancyPosture() 对无法识别的值抛错,错误文案自称:

Invalid OS_TENANCY_POSTURE="bogus". Expected one of: single, group, isolated (or the legacy alias 'multi' = 'isolated').
Refusing to boot rather than silently falling back to a posture with no organization wall.

content/docs/deployment/environment-variables.mdx:85 也照此写:「An unrecognized value refuses to boot」。

实际发生的顺序(静态追踪,origin/main @ c89d18c)

  1. packages/cli/src/commands/serve.ts:1772 const tenancyPosture = resolveTenancyPosture(); 位于 AuthPlugin 那个大 try 内(更早的 kernel.use(new AuthPlugin(...)) @ serve.ts:1692 也会经 packages/plugins/plugin-auth/src/auth-plugin.ts:470 触发同一次解析)。
  2. 该 try 的 catch 在 serve.ts:1942-1948,只打印一句黄字 ⚠ AuthPlugin failed to load: <msg> 就继续。于是非法 posture 的第一次抛错被吞,启动继续进行,只是没有 plugin-auth。
    • 这一点代码自己就写明了:serve.ts:1816-1819 的注释说「this catch sits inside the broad AuthPlugin try below, which swallows errors — a throw would be caught and boot would silently continue degraded」,D5 闸门因此特意用 process.exit(1) 而不是 throw。posture 解析的抛错没有这层保护。
  3. await runtime.start() @ serve.ts:2560 —— HTTP 服务已经起来并绑定端口。
  4. printServerReady({ ..., tenancyPosture: resolveTenancyPosture() }) @ serve.ts:2698-2716 —— 同一个解析器第二次抛错,这次不在 AuthPlugin 的 try 里,落到外层 catch serve.ts:2764-2773,printError + this.exit(1)。

结论

退出码这一半是对的(最终 exit 1),但「refusing to boot」的另一半不成立:

  • 进程在报错前已经 listen 过;
  • 运维看到的第一条信号是「AuthPlugin failed to load」,把一个 env 拼写错误伪装成插件加载问题;
  • 真正的 Invalid OS_TENANCY_POSTURE=... 只在最后由通用 printError 抛出,不是 D5 那种带修法清单的显式拒绝。

排查成本之外还有一个更实际的担忧:这条路径依赖「banner 一定会执行」这一点来兜底。任何让 printServerReady 不被执行的分支(或把 banner 的 posture 读取改成缓存值/容错读)都会把它变成「非法 posture 静默按 single 跑」——正是 env.ts 那句话要防的形态。

建议修法(不在本单实现)

在 serve 早期、任何 try 之外解析一次 posture 并显式 process.exit(1),与 ADR-0093 D5 闸门同一风格(有修法清单的 FATAL 文案),后续调用复用该值。

现状证据

packages/cli/** 下没有任何测试钉住这个行为:git grep -n "Invalid OS_TENANCY_POSTURE" packages/ 只命中 packages/types/src/env.ts:154。

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Aug 5, 2026
  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 5, 2026
  3. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    Contributor

    🔒 认领:PM 循环第 1 轮(cli 车道)
    会话:session_01VkPSGsX9o17MsGv3Lbxu2w
    分支:claude/issue-5359-serve-posture-early-exit
    Worktree:objectstack-issue-5359
    域:domain:cli
    文件面:packages/cli/src/commands/serve.ts + 钉行为的测试 + changeset。⛔ 不动 packages/types/src/env.ts 的解析器本体(其文案与语义是对的,错的是调用时机)。与同批 #5000(validate/build 路径)、#5155(runtime dispatcher)不相交。

    执行口径:按 issue 建议修 —— serve 早期、任何 try 之外解析一次 posture,非法值走 ADR-0093 D5 同风格的显式 process.exit(1)(FATAL + 修法清单文案),后续调用复用该值;补一条钉「监听发生前拒绝」的测试(现状 git grep "Invalid OS_TENANCY_POSTURE" packages/ 仅命中 env.ts 声明处,零测试)。

    同日 churn:serve.ts 所在的 packages/cli 昨日被 #5314 / #5302 触及,issue 观测基线为 c89d18c16 —— 以 origin/main 为准先核对行号与结构。


    Generated by Claude Code

  4. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    Contributor

    ✅ 验收通过 —— PR #5381,CI 全绿,转 ready 入合并队列

    对 GitHub 实况复核(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w):

    • 文件面与认领声明一致:serve.ts(+142/−2)+ 新测试 258 行 + changeset(patch),零 releases/ 触碰,packages/types/src/env.ts 按裁定未动。
    • 修复形状:posture 在 dotenvFlow.config() 之后、所有 try 之外解析一次;拒绝以 verdict 返回值而非 throw 传递(throw 正是当初被宽 catch 降级的形态)—— 这是本修复的核心属性,且被测试钉住;修法清单由 TENANCY_POSTURES 生成,不会随词表演进过期;后续读取(组织插件判断、横幅)复用同一值。
    • 测试:11 条新用例 + 方向预声明的反向核验(只回退放置位置 → 恰好只有顺序断言翻红);真机核验含副作用检查($OS_HOME 为空)。CI 23 项完成,21 成功 2 跳过,零失败。

    公开订正本单前提(dev 实测推翻,PM 采纳):标题与正文所称「拒绝发生在端口已监听之后」不成立 —— 逃逸抛错来自 kernel bootstrap 阶段一的 ObjectQL SchemaRegistry 构造,监听套接字在阶段四才打开;TCP 探针实测修复前后端口都从未绑定(对照组证明探针有效)。真实缺陷是:①归因错误(env 拼写错报成 AuthPlugin 加载失败);②带伤启动的副作用(dev crypto key 落盘);③兜底者是 ObjectQL 构造这种顺带闸门。修复把拒绝交给刻意闸门,三点全部消除。dev 以更强的「零启动副作用」顺序断言替代派发词里要求的「listen 前拒绝」断言(后者修复前也为真,恒过断言无证明力)—— 偏离成立,PM 认可。

    PR 转 ready 并挂合并队列,跟到 MERGED 为止。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions