Skip to content

finding(service-automation): registerDegradedConnector 自己那条 warn 仍把 provider 的 reason 插进 message —— #5636 修好 plugin 侧后,同一条冷启动路径上剩下的第四个接缝 #5660

Closed

Description

@os-zhuang

做 #5636 时在同一条 #3017 降级路径上扫到的,但在另一个文件(engine.ts)、另一个方法里,契约也不同,因此不在那一单范围内,按 Prime Directive #10 单开。

现象

packages/services/service-automation/src/engine.ts:1659(origin/main 5b60b3669):

registerDegradedConnector(def: Connector, reason: string, origin: ConnectorOrigin = 'declarative'): void {
    const parsed = ConnectorSchema.parse(def);
    this.assertSameOriginOrFree(parsed.name, origin);
    this.connectors.set(parsed.name, { def: parsed, handlers: {}, origin, state: 'degraded', degradedReason: reason });
    this.logger.warn(`Connector registered DEGRADED: ${parsed.name} (origin: ${origin}) — ${reason}`);
}

那个 reason 参数就是 #5636 处理的同一个值:plugin.ts 的 degradeConnectorInstance 把
ConnectorUpstreamUnavailableError.message 原样传进来,而该 message 由第三方 provider
factory
构造(ADR-0097 明确鼓励第三方去写 provider factory;spec 只定义错误类、不约束
文本),完全可以是多行。

为什么它比 #5636 修掉的那两处更容易被忽略

调用顺序决定了它先发生,而且发生在常见分支上:

  1. degradeConnectorInstance → engine.registerDegradedConnector(husk, info.reason, …)
    → 这一条 warn(husk 注册成功时就会打,也就是每次首次降级都打);
  2. 只有 husk 注册抛错时才走到 plugin 侧那条 warn(finding(service-automation): connector 降级路径(#3017)还有两处 ${err.message} 单行插值,是 #5575 之外的第三个接缝 #5636 已修);
  3. 然后才是 plugin 侧的降级公告 error(finding(service-automation): connector 降级路径(#3017)还有两处 ${err.message} 单行插值,是 #5575 之外的第三个接缝 #5636 已修)。

也就是说 #5636 修完之后,首次降级的默认路径上仍然留着一条会溢出的 warn。

危害机制(与 #5636 同一条,已实测)

  • ObjectLogger 把 warn 送 stdout,serve 的启动静默窗口只包了
    process.stdout.write,窗口从 config 加载前一直开到 banner 打印;
  • 冷启动的 materializeDeclaredConnectors(ctx, { fatal: true }) 遇到上游不可达是降级、
    不抛错
    ,所以这条路径在窗口内就会跑;
  • BootLogCapture.offer() 只在 classifyBootLogLine 能在物理行上找到 <ts> <LEVEL> 头时
    才保留该行,续行直接丢弃。

#5636 的 PR 里对一份 13 行的插值 ZodError dump 实测:写出 13 行,缓冲保留 1 行(止于
Zod [ 的头行)、丢弃 12 行。这里的载荷不是 ZodError 而是 provider 的 message,机制一样。
即 cloud#971 的原始形态。

可达性(为什么是 finding 而不是 bug)

与 #5575 / #5636 同样的理由:今天 packages/connectors/*/src 里 ConnectorUpstreamUnavailableError
的 message 在 openapi / mcp / rest / slack 里都是我们自己写的单行文本。第一个把上游 SDK 的
多行错误塞进该错误类的第三方 provider 插件会撞上。

修法上需要先定的一件事(不是零决策)

与 #5636 的两处不同,这里没有抛出值可用:registerDegradedConnector 的签名只收
reason: string。而且这个字符串同时是 degradedReason,会经 GET /connectors 和
connector_action 被拒时的文本出去 —— #5636 刻意保持它逐字不变(人透过 JSON 读,不经按行
切分的消费者)。所以至少两条路:

倾向 A:它与 #5048 / #5575 / #5636 建立的形状一致(抛出值一路带到报告点,不在边界先
拍平成字符串),而且 ConnectorUpstreamUnavailableError 本身还带一个 cause(底层 connect
错误),A 之后才有可能把它也渲染出来。B 更小,但把「reason 是给人读的文本」和「日志要结构化」
这两件事压在同一个字符串上,下一个接缝还会再遇到。

关联

#5636 / PR(plugin 侧两处,同一条路径)、#5575 / PR #5639、#5048 / PR #5572、#5573(脱敏按
子串匹配)、#4632、#3017(降级/重试本身)、cloud#971、ADR-0097。

Activity

  1. added
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 5, 2026
  2. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    发现分诊(#4949 纪律):晋级——摘 finding 换 pm:queue,保留 domain:services。

    一行理由:同族先例 #5048 / #5575 / #5636 已确立形状并全部入队(#5636 上轮晋级),本条是同一冷启动路径上默认分支先发生的那条 warn,可达性与家族一致;修向裁定按 A 路(registerDegradedConnector 签名加可选 cause?: unknown,warn 经 describeThrownForLog 走 meta,reason / degradedReason 一字不动)——与家族既定形状同源,B 路正文已自述会把两种职责压在同一字符串上。落点 service-automation/src/engine.ts:1659。车道注意:#5636 若仍在飞,同路径(不同文件)按依赖顺序串行。维护者可否决。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. self-assigned this
    on Aug 6, 2026
  4. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    认领(services 车道 PM 派发,session_01BWS4heBoAitLmzCLhcYdbK)


    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

domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions