Skip to content

zodShapeOf 的 unwrap 面比三个同族 walker 窄:缺 union 分支与 prefault(#5317 修完管道方向后剩下的两格) #6098

Closed

Description

@os-zhuang

发现于 #5317 实施途中(observation-class:两格今天都被 fail-closed 兜住,没有已知的错误产物)。

packages/spec/scripts/lib/zod-graph.ts 的 zodShapeOf 是 #5317 从 build-schemas.ts 抽出来的;它有三个同族 walker,做的是同一件事(把一个 Zod 节点解到对象 shape):

  • packages/spec/scripts/liveness/check-liveness.mts 的 unwrap / shapeOf
  • packages/spec/src/kernel/metadata-authoring-lint.ts 的 unwrap / keyPosture
  • packages/spec/src/system/metadata-form-zod-reconciliation.test.ts 的 unwrap / keysOf

#5317 已经把管道方向这一格对齐了(z.preprocess 走 OUT)。剩下两格仍然只有 zodShapeOf 缺:

事实 1 —— 没有 union 分支

zodShapeOf 只认 object,遇到 union 返回 null。三个同族 walker 都合并 union 成员的键(check-liveness 的 shapeOf 带着 #3095 的注释,说的正是「一个 metadata type 可能注册一组 shape 的 UNION 而不是单个对象」)。

后果,#5317 实测:view 是注册表里唯一的 z.preprocess 根,其 OUT 是 z.union(#5074 的 console-decoration 剥离)。#5317 把方向修对之后,pipeAuthorableSide(view) 从 transform 变成了 union —— 但 zodShapeOf(view) 依然是 null,因为没有 union 分支。所以「preprocess 根能被解出真实 shape」这句话对 view 今天仍然不成立,方向修对是必要但不充分。

packages/spec/scripts/zod-graph.test.ts 里有一条测试把这个状态诚实钉住了(documents that view's preprocess OUT is a union, so it still derives no shape),加 union 分支的人会先撞到它。

事实 2 —— wrapper 集合缺 prefault

zodShapeOf 的 SHAPE_WRAPPER_TYPES 是 optional / nullable / default / catch / readonly / nonoptional。三个同族 walker 的对应列表都还有 prefault。一个 prefault 包着的对象在 zodShapeOf 下解不出 shape。

#5317 特意没有顺手加:它没被测量过,而 zodShapeOf 喂的是 derived-clone 桥,任何解出更多 shape 的改动都会新增桥项。

为什么是 observation-class 而不是缺陷

两格的后果都落在 computeSurfaceReachability 的 reachableVia,而那里解不出 shape 时 return 'root-graph'(fail closed —— 宁可多要一个 tombstone,不静默放宽)。所以当前表现是保守,不是错答;而且 reachableVia 只在 #4650 删除门禁发现「基线行被删」时才被调用,平时的 gen:schema 根本不走。今天没有用户能撞到。

修的时候注意(#5056)

这两格任何一格补上,都会让更多节点解出 shape,从而给 bridged 表新增 (propName → propSchemaInstance) 项 —— 这正是 #5056 提醒过的、会把死形状标成可达(null → derived-clone)的桥。所以不是加一行就完事:

两格建议放同一个 PR 一起量:它们改的是同一个函数的同一片 unwrap 面,分开做要把上面那套测量跑两遍。

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 7, 2026
  2. claude commented on Aug 7, 2026

    @claude
    Contributor

    Findings triage — first grading (this issue had never been graded). Verdict: HOLD as finding, + domain:spec-tooling, with a mechanically checkable restart condition.

    Why hold, not promote. The observation-class argument was checked, not accepted on trust, and it holds: both gaps land in computeSurfaceReachability's reachableVia, which returns 'root-graph' when no shape resolves — fail-closed, so the current behaviour is conservative (an extra tombstone), never a wrong answer; and that path only runs when #4650's deletion gate sees a baseline row removed, not during ordinary gen:schema. Nothing a user reaches today, which is exactly the finding criterion rather than the queue's.

    And a hard serial reason on top of the grading. PR #6102 (draft, for #5317) is rewriting packages/spec/scripts/lib/zod-graph.ts and zod-graph.test.ts right now — the same function and the same test file this issue is about. Queuing it today would put a second agent inside a file already in flight, on work whose own body describes it as "what remains after #5317". Even on the promote reading, the correct state would be pm:queue + pm:blocked; holding is the more honest of the two, since the observation grade stands on its own.

    Restart condition (checkable, no memory required): PR #6102 merges (or #5317 closes) ⇒ re-grade this issue in the next findings round, with promotion as the expected outcome. At that point the file is free, the pipeline-direction cell is settled, and the body already carries the measurement protocol a dispatch would need (dump every def's reachableVia before and after, explain each moved verdict, run gen:schema + check:generated, and account for any entry that becomes derived-clone — the #5056 bridge hazard). Both cells go in one PR, as the body argues, so the measurement runs once.

    Also noted for the re-grade: packages/spec/scripts/zod-graph.test.ts currently pins the honest present state ("documents that view's preprocess OUT is a union, so it still derives no shape"). Whoever adds the union branch must change that test — a green test deleted without comment would erase the record of why the gap existed.

    Routing. domain:spec-tooling, matching its parent #5317 (same file family, packages/spec/scripts/**) so the pipeline-direction fix and its leftovers stay in one lane. ⚠️ Recording a taxonomy gap for the maintainer rather than papering over it: domain:spec-tooling is in active use on six open issues but does not appear in the SKILL's domain table, whose domain:spec row reads packages/spec 及其生成物. Existing usage was followed here for lane consistency; the table should either gain the row or the label should be retired, by PR — a label the table cannot explain will eventually be applied two different ways.

    Dedup. Full paginated enumeration of open issues and PRs across the three repos (search API unavailable to repository-scoped sessions). #5317 / PR #6102 is the parent, not a duplicate — it fixed the pipeline direction only, and this issue is explicitly the residue. No other open record covers zodShapeOf's unwrap surface.

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


    Generated by Claude Code

  3. claude commented on Aug 7, 2026

    @claude
    Contributor

    Findings triage (objectstack#4949 discipline) — restart condition FIRED ⇒ PROMOTE to pm:queue. Domain unchanged: domain:spec-tooling.

    1. The trigger, verbatim from the previous verdict

    Restart condition (checkable, no memory required): PR #6102 merges (or #5317 closes) ⇒ re-grade this issue in the next findings round, with promotion as the expected outcome.

    Both halves fired, ~1.6 h apart from this round: PR #6102 merged 2026-08-07T03:37:02Z, #5317 closed 2026-08-07T03:37:03Z.

    2. Stale-premise check (origin/main @ ede5a8e) — both gaps still stand

    • union branch, absent. zodShapeOf (packages/spec/scripts/lib/zod-graph.ts:165) branches on exactly four shapes — object, pipe (via pipeAuthorableSide, which is what fix(spec): zodShapeOf 按管道方向解 z.preprocess 的授权面(#4488 盲点的第四个现场) #6102 rewrote), lazy, and SHAPE_WRAPPER_TYPES — then falls through to return null. No union arm.
    • prefault, absent. SHAPE_WRAPPER_TYPES (:87) is exactly optional, nullable, default, catch, readonly, nonoptional. The sibling walker at packages/spec/scripts/liveness/check-liveness.mts:225 does unwrap prefault, so the asymmetry the body reports is live.

    3. The merged code names this issue as the owner of the remainder

    zod-graph.ts:83-86, landed by #6102:

    prefault — which the three sibling walkers below do unwrap — is knowingly absent; adding it is a separate, separately measured change (filed as its own finding, not smuggled in here).

    The deferral was deliberate and this is the designated follow-up, which removes the "is this in scope for someone else?" question a dispatch would otherwise have to answer first.

    4. The hard serial reason for the previous hold is gone

    The last verdict held partly because #6102 was rewriting zod-graph.ts and zod-graph.test.ts in flight. Enumerated all 5 open PRs in this repo this round (#6171, #6165, #6164, #6160, #4935) — none touches packages/spec/scripts/**. The file is free.

    5. Not a release blocker

    ⛔ No target:<major>: computeSurfaceReachability's reachableVia returns 'root-graph' when no shape resolves, so the behaviour is fail-closed (one extra tombstone, never a wrong answer), and the path only runs under #4650's deletion gate — not a shipped-surface defect on the binary criterion.

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


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 7, 2026
  5. self-assigned this
    on Aug 7, 2026
  6. os-zhuang commented on Aug 7, 2026

    @os-zhuang
    ContributorAuthor

    认领:PM 循环第 5 轮(spec-tooling 车道,座位登记 #6018;分诊座位已核 restart condition 双半触发并晋级)
    会话:session_014wsZeReNTqiceBfLb5Pyf5
    分支:claude/issue-6098-zod-graph-unwrap-parity
    Worktree:objectstack-issue-6098
    域:domain:spec-tooling
    文件面:packages/spec/scripts/lib/zod-graph.ts(union 分支 + prefault 两格)+ packages/spec/scripts/zod-graph.test.ts(含把 view 现状钉死的那条测试——补 union 后它必然要改,这是预期的,改动要在 PR 里逐条说明)+ 分片生成物(若可达性判定移动)+ .changeset/*.md。⛔ 不碰 build-schemas.ts(#5371 本轮并行)、lib/file-description.ts、lib/format-type.ts、zod 文件。(越界即停,报告说明)

    ⚠️ #5056 测量轮是本单的核心要求:两格任一补上都会让更多节点解出 shape、给 bridged 表新增桥项。派发令要求逐条解释每个新增桥项,并特别标注任何 root-graph → derived-clone 的迁移(那正是 #5056 警告的假可达面)。#5317 的 dev 特意没有顺手加这两格,就是因为它需要独立的测量轮——本单就是那一轮。


    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

pm:dispatchedDispatched to a dev agent by /pm-dispatchpm: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