fix(metadata-protocol): 分层读的 overlay 读失败不再被画成「这一项没有定制」(#5707) - #5843
Merged
Conversation
…lay instead of "no overlay" (#5707) The layered read's `sys_metadata` overlay lookup was wrapped in a bare `catch` that fell through to `overlay: null`. That is not a neutral placeholder: the envelope then makes three positive claims at once, with HTTP 200 — `overlay: null` ("never customised"), `overlayScope: null` ("no scope holds a row") and `effective === code` ("what runs today is the packaged artifact, verbatim"). The Studio diff tab exists to answer "what did I change?"; during an outage it answered "nothing". Same defect and same discrimination as #5532 / PR #5705, on the one overlay read that PR deliberately did not reach (scope = the issue): `rethrowUnlessMetadataStoreUnprovisioned` lets `isMissingTableError` through (an unprovisioned `sys_metadata` genuinely holds no overlay row, so first boot still renders the code layer) and turns every other error into `503` / `SERVICE_UNAVAILABLE` with the driver error on `cause`. No new return shape: the envelope stays a 3-LAYER shape, not a 3-VALUE one. Coverage joins the same-family outage file rather than a new isolated one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 6, 2026 08:03
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 #5707
getMetaItemLayered的sys_metadataoverlay 读裹着一个裸catch,故障期把「读不到定制」画成「没有定制」。本 PR 复用 PR #5705 引入的rethrowUnlessMetadataStoreUnprovisioned,与该 PR 的四处 overlay 读、#5706 的锁闸读同语义。没有新增返回形状——issue 正文提到的「给 overlay 加第三态」那条路没有走(那是新契约,需维护者拍板),下文「裁定」一节说明为什么不需要。一、第一步是测量:谁消费三层视图,故障期各自怎么处置
分诊晋级评论要求先测量再选修向。逐个消费方核实结果如下(objectstack 与 objectui 均以各自
origin/main为准)。1. 故障期今天到底发回什么(实测,不是推断)
在
packages/metadata-protocol用「所有读都 rejectconnect ECONNREFUSED 10.0.0.5:5432」的引擎跑修复前的代码,getMetaItemLayeredresolve 出这个信封(测试断言失败信息里逐字打印):HTTP 200,三个正面断言同时成立:
overlay: null(从没被定制过)、overlayScope: null(两个作用域都没有行)、effective === code(生效的就是打包件原样)。没有任何字段能让消费方看出这是一次失败的读。另一条实测:org 作用域读失败时,env 行即使完全可读也一并被吞——两次 lookup 在同一个
try里,org 先跑,抛出即跳过 env。2. REST:
GET /meta/:type/:name?layers=truepackages/rest/src/rest-server.ts的 layered 分支与普通读共用同一个 handler 的try/catch→handleRouteError,#5532 已由packages/rest/src/rest-meta-outage-vs-miss.test.ts钉住该信封落地为503 + SERVICE_UNAVAILABLE(文案按 5xx 规则 withheld,驱动错误经cause进日志)。所以 REST 侧零改动、零新分支。3. objectui / Studio:有直接消费,且早已有错误通道
PM 预扫「未见直接命中」需要更正:objectui 消费得相当广。
packages/data-objectstack/src/metadata-client.ts的MetadataClient.layered()拼的正是?layers=true,而它 对非 2xx 一律throw parseError(res),只有 404 被映射成空信封。调用点:metadata-admin/ResourceEditPage.tsx(4 处)try/catch→setError/setLoadFailed,渲染错误态metadata-admin/EmbeddedItemEditor.tsxtry/catchmetadata-admin/PermissionMatrixEditor.tsx.catch(() => null)降级studio-design/PackageOwdOverviewPanel.tsx.catch(() => null)降级studio-design/StudioDesignSurface.tsx(4 处)关键结论:503 不引入任何新形态。这些分支今天就要处理网络错、502、以及 #5532 之后普通读的 503;改动只是让「后端读不到」从谎报 200 变成走那条本来就存在的错误通道。反过来,前提里担心的「整体空白」恰恰是现状的风险面更大:今天作者看到的是一个渲染完整、内容为假的 diff 视图。
4. plugin-security:三个消费点,其中一个没有 try/catch —— 实测它
packages/plugins/plugin-security/src/permission-set-projection.ts有三处:effectiveBodyForRow(~618):有try/catch,回落permissionSetBodyFromRow(row);reconcilePermissionSetProjection(~912):有try/catch,layered = null;而它的外层在security-plugin.ts(~1927)又整体裹了try/catch+ warn;projectPermissionMutation(~480):没有 try/catch —— PM 点名要测的就是这处。实测方式:把上面实测到的故障期信封,喂进真实的
projectPermissionMutation(临时 harness,已删除),记录它对sys_permission_set的写:UPDATE sys_permission_set {"id":"ps_1","label":"Contributor","customized":false}—— 定制被静默改回打包基线,customized标记被抹掉DELETE sys_permission_set {"id":"ps_1"}+registry.unregisterItem(permission,...)—— 一次读失败把权限集记录直接退役503 SERVICE_UNAVAILABLE,写次数 0503 SERVICE_UNAVAILABLE,写次数 0也就是说:这个安全面消费者在「静默 null」下的行为不是「退回 code 基线」这么温和——无 declared body 时它会删记录。503 化把一次破坏性写换成一次拒绝。
那这个抛会不会「把权限投影整体打挂」?测量三个入口:
runMutationProjector(protocol.ts)的契约就是 never throws —— 捕获后返回{ success: false, error },由saveMetaItem/publishMetaItem/deleteMetaItem作为projectionApplied附在响应上。既有测试已钉:packages/metadata-protocol/src/mutation-listeners.test.ts的 "a throwing projector is surfaced as { success:false, error }, never thrown"。故写路径不会因此 500,只会如实报告投影没做成。security-plugin.ts对reconcilePermissionSetProjection整体try/catch+ warn,启动不会被打断;况且它先读sys_metadata才进循环,读不到时循环根本不跑。projectAndFetch(insert/update 路径)会把 503 上抛给调用者。这正是应该的:该路径此前的静默结果是「saveMetaItem成功了,记录却被按 A/B 两种方式写坏或删掉,并返回一个残缺 record」。前提核对结论:PM 给的前提成立 —— 503 化不会让任何测量到的消费面整体空白,也不会让 permission-set projection 比「静默退回基线」更糟;测量显示恰恰相反。
5. protocol.ts 里那句 "3-state diagnostic shape"(~7467)
那句话在
saveMetaItem的防御分支里,指的是{ code, overlay, overlayScope, effective }这三层信封(用于识别有人把读信封原样 PUT 回来),不是「每层三态」。本 PR 不动信封形状,所以该注释无需校准,措辞与改动一致。为免后人再把两者混读,getMetaItemLayered的 TSDoc 里补了一句:层是 3-LAYER 不是 3-VALUE,「读不到」没有对应的层值,所以照失败上报。二、裁定:复用 helper 走 503,不发明第三态
按预裁执行,前提已由上面的测量证实。补一条支持理由(不代维护者拍板,仅记录为什么第三态在这里也非必要):三层信封里的
null已经被占用为「没有定制」,再往同一个字段塞「未知」需要新的取值词汇 + 全部消费方跟进;而所有实测到的消费方都已经有错误通道,拿不到收益。真需要「部分可用的诊断视图」时,那是新契约,应由维护者立单。改动本身是一处 catch:
isMissingTableError(表尚未建 → 确实没有 overlay 行)良性放行,首次启动照旧只渲染 code 层;其余抛status: 503/code: SERVICE_UNAVAILABLE,驱动错误挂cause。一个次级事实,顺带记录、本 PR 不处理:ADR-0010 的
lock/editable/deletable三元组是从同一批层里算的(lockSource = code ?? overlay ?? {}),所以「只有 overlay、没有 code」的项在故障期还会额外报出lock: 'none'。那是读侧展示,写侧闸门已由 #5706 fail-closed;503 化之后这个展示也一并不再产生。(resettable只由artifactBacked决定,与 overlay 无关,不在此列。)三、测试落点与反向验证
覆盖并入同族文件
packages/metadata-protocol/src/protocol.metadata-store-outage.test.ts(#5705 为getMetaItems/getMetaItem建立的那批),不新开孤立文件:将来谁把其中一处 catch 重新放宽,会是同一个文件里的 diff,且共用expectStoreUnavailable,信封不会按方法各自漂移。新增 6 例:SERVICE_UNAVAILABLE/ErrorCode可解析 / 文案含 "unknown" 不含 "not found" /cause是驱动错误本体);effective === code,outage 变 503;overlay/overlayScope为 null、effective === code);overlayScope: 'env'。反向验证(方向在跑之前先写下:普通红)。 把
} catch { /* DB unavailable — overlay stays null */ }放回去,预测「3 红 / 3 绿」——3 个故障用例转红(resolve 而非 throw),3 个 benign/healthy 保持绿,以此证明改动是「故障与良性的分流」而不是「layered 现在会抛」。实测与预测一致:上面第一条的失败文本,就是本 PR 第一节引用的那个「故障期实测信封」的来源。
既有的
packages/objectql/src/protocol-layered-get.test.ts(layered 的行为测试)全部照旧通过——它们用的引擎都能正常应答,不依赖这处吞异常,无需 fixture 分诊。四、验证
已加 changeset(
@objectstack/metadata-protocol: patch)。五、给后续同文件单的定价回答(PM 必答项)
saveMetaItem的两处成功 message 模板(写路径),与本 PR 的读路径 catch 无共享代码、无共享测试。仅同文件,merge 面不重叠。getMetaItems的 registry 视图。唯一间接关系:该单的排查要对照 listing / layered / dispatch 三面,而故障期 layered 不再谎报,只会让对照更可信;工作量不变。record.packageId恒 undefined)loadMetaFromDb)、另一类 bug(snake_case 键名读错),需要的是 boot-hydrate pin 测试,与本 PR 的 outage 覆盖不相交。注意同文件串行即可。六、范围外发现(已单独立单,未在本 PR 修)
MetadataManager.get()丢弃loadDiagnosed的degraded判定。 正是本方法 code 层那一侧的同族点:metadataService.get(...)在 loader 失败时返回undefined,与「这一项没声明」不可分辨,而loadDiagnosed的 TSDoc 明确写了不许这样读;判定被算出来又在两跳内丢掉。6 个消费点,含 plugin-security 的 evaluator 同步。注意:getMetaItemLayered的 code 层虽然也裹着catch { /* ignore */ },但对 loader 失败是空转(get()根本不抛),修在 catch 上修不掉,所以没有并进本 PR。loadMetaFromDb用/no such table/i正则判良性,其余读失败吞成console.warn+loaded: 0。 同族的 boot 侧;良性判定是isMissingTableError的手抄第二份,而那个谓词就在同一个文件里被 import 着用(DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108 / [metadata-protocol] SysMetadataRepository 的 nextEventSeq()/nextItemVersion() 同样把读失败当「表还没建」,静默从 1 重新发号 —— #4825 在 canonical 路径上的同形缺陷 #4867 / PR fix(metadata-protocol): 元数据存储读不到不再被讲成「这一项不存在」 (#5532) #5705)。两条都按 Prime Directive #10 立单、unassigned,严重度交 PM 分诊。
Generated by Claude Code