Skip to content

finding(service-automation): engine.ts 的 resumeInternal 把驱动错误插进 message —— #5737 修完后,这条 resume 路径上仅剩的一处外来 cause 拼接 #5912

Description

@hotlong

做 #5737(PR #5911)时在同一条 resume 路径上扫到的旁生发现,但在另一个文件、另一个方法里,因此不在那一单范围内,按 Prime Directive #10 / objectstack#4949 单开。

现象

packages/services/service-automation/src/engine.ts:3014(origin/main 7b005b4),resumeInternal 读挂起态存储失败的那一支:

try {
    run = await this.loadSuspendedRunStrict(runId);
} catch (err) {
    const message = (err as Error).message;
    this.logger.error(
        `[automation] durable suspended-run store unreachable while resuming '${runId}': ${message}`,
    );
    return {
        success: false,
        code: 'STORE_UNAVAILABLE',
        error: `Durable suspended-run store unreachable for run '${runId}' — retry once the store is available: ${message}`,
    };
}

message 来自 loadSuspendedRunStrict,即数据源驱动自己的失败文本 —— 我们不控制它有几行。它被插进 logger.error 的 message(第 3017 行)。

为什么 #5737 没有覆盖它,以及为什么现在它更显眼

#5737 修的是 builtin/wait-node.ts 的五处。其中 :98 那处的 cause 正是这里第 3022 行造出来的信封字符串 —— 也就是说驱动的多行文本经这个信封两跳到达 wait-node 的记录。PR #5911 已经把信封那一跳接住了(落 meta),但第 3017 行这条 logger.error 自己照旧拼接。

所以修完 #5737 之后,同一次「resume 时存储不可达」会产生两条记录:wait-node 那条现在是干净的一行,engine 这条仍会被切碎。

危害机制(与同族四单同一条)

ObjectLogger.write() 一次调用只加一个「时间戳 + 级别」记录头,message 里的换行把一条记录变成多个物理行,后几行无级别无时间戳。pretty / text(os dev / os serve 默认)下文件 sink 当独立记录存,grep ERROR 只捞到不含事实的那一行。error 走 stderr,不经 serve 的启动静默缓冲,所以危害是被误读而非被丢弃(与 #5661 第 2、3 条同形)。cloud#971 即此形态。

级别本身是对的:存储不可达而运行仍在盘上,是 #4632 定义的耐久性降级,error 不应下调。

可达性(为什么是 finding)

与 #5575 / #5636 / #5661 / #5737 同样的理由:今天库内的驱动错误均为单行,所以这是 finding 而不是事故报告。第一个包装多行 SDK 错误的驱动撞上 —— 数据库驱动的多行错误(Postgres 的 detail: / hint: 续行、better-sqlite3 包装器)在生态里很常见,#5737 的实测就是照这个形状造的。

修法(同族既定模板,零新词汇)

复用同包 thrown-cause-diagnostics.ts 的 describeThrownForLog:message 保持单行自足,cause 走 meta。按 Logger 契约(packages/spec/src/contracts/logger.ts)选参数位 —— error(message, error?, meta?) 用第三参,第二参传 undefined,否则每条记录都带整个栈(#5575)。新增字段名不得含 key / token / secret / password 子串(#5573)。

一个需要顺带判断的点(不是阻塞项):第 3022 行的信封字段 error 是否也该停止插值。倾向不动 —— 它是给调用方读的结构化返回值(经 REST 出去时是 JSON,不经按行切分的消费者),#5636 对 degradedReason 就是刻意保持逐字不变的;而且 PR #5911 已经让 wait-node 侧把它整体放进 meta,多行文本由 JSON.stringify 转义后完整保留。

关联

#5737 / PR #5911(wait-node 五处,本发现的来源)、#5661(plugin.ts 三处)、#5636 / PR #5662、#5575 / PR #5639、#5048 / PR #5572、#5660(同在 engine.ts 但在 registerDegradedConnector,已闭)、#5573、#4632、#4420、cloud#971。

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions