fix(objectql): sys_file hydrate 读故障与「无文件」可分辨 (#6116) - #6456
Merged
Conversation
`resolveFileReferences` 的 sys_file 批量查找此前坐在裸 `catch { return
records }` 后面:连接中断、超时、权限拒绝、查询错误,与良性的「表还没建」
一样,都被同一次静默的裸 id 穿过所回答。消费方(UI / 导出)拿到裸 id 后按
「无附件」渲染,故障期间的表现与「记录本就没有文件」不可分辨 —— ADR-0110
D3 的族形,只是载体是功能面而非持久性面。
fail-open 行为本身不变(这是可诊断性修复,不是行为修复):文件元数据读失败
不该拖垮发起它的记录读,所以两个分支都仍然原样返回入参。变的是两种原因不再
共用同一份沉默。
catch 现在按错误类型分流,走仓内既有的 `isMissingTableError` 判别器
(`@objectstack/metadata/errors`),与本文件 `seedAutonumber` 同一调用,而不是
手抄一份 `code === '42P01'`:
- 表未建:确实没有已提交的行,未 hydrate 的答案就是真相,保持静默穿过;
- 其他读故障:一条 `warn`,带父对象、未 hydrate 的字段、未解析 id 数、driver
自己的报错、后果(这些 id 本次读会渲染成「无文件」)与修法。每次读说一次,
不是每条记录或每个 id 说一次。
按 AGENTS「Degradation log levels」判定为 warn 不升 error:此路径不声称任何
持久化,损失是功能性的、只影响本次响应,下一次成功读即修复。
一处对 issue 正文的实测更正:catch 位确为零输出,但整条路径并非字面无声 ——
上一帧的通用读处理器已在重抛前记 `Find operation failed`。该行未被触碰,也不
构成替代:它对良性与非良性故障逐字相同,且只描述 sys_file 子读,从不提及父
对象、被留作裸 id 的字段,或那个仍然返回给调用方的降级答案。测试中
`the pre-existing generic line cannot tell the two apart` 一节把这点钉住。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
…-file-hydrate-fault
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 14 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
CI 的 `check:query-options-erasure` 棘轮判红:新测试把 test 面从 263 抬到
273 —— 10 处 `engine.find('doc', {} as any)` / `findOne(..., { … } as any)`。
这些入参本来就在契约内,不属于「故意越界的拒绝测试」,所以按棘轮给的第一条
remedy 处理:上类型,而不是 `as unknown as EngineQueryOptionsParsed`。
`find` / `findOne` 的 query 形参本身可选,`{}` 直接省略即可;findOne 的
`{ where: { id: 'd1' } }` 去掉断言后原样通过 `EngineQueryOptionsParsed`。
test 面回到上限 263,断言与用例数不变(21 例仍全绿)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6116
resolveFileReferences把存成sys_file不透明 id 的文件字段值富化成{ id, name, size, mimeType, url }(ADR-0104 D3 wave 2)。它那一次批量查找此前坐在裸
catch { return records }后面:连接中断、超时、权限拒绝、查询错误,与良性的「表还没建」一样,都被同一次静默的裸 id 穿过所回答。消费方(UI / 导出)
拿到裸 id 后按「无附件」渲染,故障期间的表现与「记录本就没有文件」不可分辨 ——
ADR-0110 D3 的族形,只是载体是功能面而非持久性面。
fail-open 行为本身不变,这是可诊断性修复,不是行为修复。 文件元数据读失败
不该拖垮发起它的记录读,所以两个分支都仍然原样返回入参,没有任何读开始抛错。
变的是两种原因不再共用同一份沉默。
前提复核表(动手前逐条实测)
catch { return records }零日志形状packages/objectql/src/engine.ts的resolveFileReferences内、sys_file批量读之后,原文为} catch {+return records; // sys_file unregistered / unreadable — leave ids as-is。更正:catch 位确为零输出,但整条路径并非字面无声 —— 见下节。isMissingTableError存在且是仓内同职良性判别器的标准判法engine.ts第 76 行import { isMissingTableError } from '@objectstack/metadata/errors';现用调用点在同文件seedAutonumber(#5979 落地),形状完全同族:良性 ⇒ 保持廉价答案,非良性 ⇒ 不当作良性处理。闸门的READ_FAILURE_DISCRIMINATORS也只登记了这一个判别器。getDiagnosed是否需要改调用方契约getDiagnosed是MetadataManager上的读 API,返回{ data, degraded, errors }。本处resolveFileReferences是 private 方法,两个调用点是find(engine.ts 内)与findOne;要把判定真正交到「调用方」手里,信封必须一路透出到find/findOne的公开返回契约上。分诊原话是「仅当不动契约时可选」,故排除。P1 的更正:通用行存在,但它不是判别器
实测(fake driver 注入故障 + 捕获 logger):上一帧的通用读处理器
engine.ts的'Find operation failed'已在重抛进本 catch 之前记了一行error,meta 为{ object: 'sys_file' }。所以 issue 正文的「不记一行日志」在catch 位上成立,在整条路径上不精确。
该行未被本 PR 触碰(分诊红线:不触 engine.ts 其他区域),也不构成对验收的
替代 —— 测试里
the pre-existing generic line cannot tell the two apart一节把两条理由钉住:
['Find operation failed']),所以只读这一行的运维无法判断手上的答案能不能信;
sys_file子读,meta 里没有doc也没有attachment—— 从不提及父对象、被留作裸 id 的字段,或那个仍然返回给调用方的降级答案。
验收要求的「可分辨」正是这两点,所以修复照做。
取舍:为什么取最小形
取最小形(catch 内按良性判别器分流 + 非良性一条
warn),不取getDiagnosed形:getDiagnosed形的收益是把判定交给调用方,但本处真正的「调用方」是find/findOne的公开返回值。要透出信封就得改这两个公开契约,而本单是可诊断性修复,分诊也明确只在「不动契约」时才允许该形 —— 契约要动,所以不动;
seedAutonumber(
isMissingTableError),日志形状对齐紧邻上方的expandRelatedRecords—— 那个 catch 同样是 fail-open 保留裸外键 id,同样记一条
this.logger.warn('Failed to expand relationship field; retaining foreign key IDs', { object, field, … })。file 字段的 hydrate 就是 lookup 展开的孪生路径,孪生路径已经在 warn,这条不该继续沉默。
分流后的两个分支:
答案就是真相 ⇒ 保持现状,静默穿过。反过来在这里出声会让所有还没同步存储
schema 的应用每次读都刷一行,正是让真实告警变得不可读的噪音;
warn,带父对象、未 hydrate 的字段、未解析 id 数、driver自己的报错、后果与修法;每次读说一次,不是每条记录或每个 id 说一次。
warn不升error,按 AGENTS「Degradation log levels」的判定问句:此路径不声称任何持久化,损失是功能性的、只影响本次响应,下一次成功读即修复。
闸门词表表态(必答项)
结论:该扩,但作为一条新判据,而不是往 EMPTY 值表里塞一项;且扩之前必须先量
假阳性面。已按 Prime Directive #10 立独立单 #6451(未认领,
finding标签),⛔ 未夹带进本 PR(闸门在
scripts/,devx 车道)。理由摘要:
[]/null完全同族 —— 读没发生,调用方却拿到一个与合法的「空 / 无」不可分辨的答案。规则名叫 invention,但它真正保护的是可分辨性;
契约(inline blob、外部 url 字符串本来就原样穿过),不是编造。要把「作为契约的
穿过」与「吞掉故障的穿过」分开,需要知道函数的声明语义,而这条语法规则看不到 ——
与脚本头部自陈的局限 1(信封形状判不了)是同一类盲区;
任意级别日志 ⇒ 豁免;按错误类型判别 ⇒ 豁免)。本 PR 修完后这两条豁免都命中,
所以将来扩了也不会把这个点判红。
实测佐证:闸门在修复前后都报绿(
65 read seam(s), none invents an unreported empty answer)—— 缺陷在闸门下存活到人工清扫才被看见,这正是 #6451 的实证样本。反向验证表(方向先写死,再跑)
预测(动手前写定):把分流逻辑回退成裸
catch { return records }⇒ 非良性warn断言转红,fail-open pin 保持绿。回退实测输出:
Tests 6 failed | 15 passed (21),红的恰好是read outage → exactly one warn that names the loss整组。恢复修复后21 passed (21)。命令输出
objectql TEST_DEBT 台账 355、本次实测 347(低于派单时提到的 351),新增代码
引入的 tsc 错误为 0,⛔ 未抬账。
红线遵守
engine.ts已裁定的显式三态{ verified: false, conclusive: false }(referenceExists一带);scripts/check-durability-degradation-log-level.mjs;content/docs/releases/;changeset 为 patch@objectstack/objectql。测试
新增
packages/objectql/src/engine-file-hydrate-outage.test.ts(21 例),驱动 fakedriver(非 fake engine),不涉及 engine 写动词分发契约,故与
check:engine-double-contract无交集。良性 3 类(PG 42P01 / MySQL ER_NO_SUCH_TABLE /SQLite message-only)、非良性 4 类(ECONNREFUSED / statement timeout / permission
denied / connection terminated)分别成组。
Generated by Claude Code