Skip to content

fix(spec): #4650 删除闸门改用树内基线锚点,按 SHA 钉住的离线消费者构建不再硬失败 (#5235) - #5304

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-5235-offline-surface-anchor
Aug 4, 2026
Merged

fix(spec): #4650 删除闸门改用树内基线锚点,按 SHA 钉住的离线消费者构建不再硬失败 (#5235)#5304
os-zhuang merged 4 commits into
mainfrom
claude/issue-5235-offline-surface-anchor

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5235

按维护者指示走方向 2:把删除闸门的基线锚点固化成树内产物,同时保住 #4650 的防旁路性质。

问题

resolveSurfaceBase() 只从 git 取基线(origin/main + 自愈 fetch),两条都拿不到就 process.exit(1)。对"忘了 fetch 的开发者"这是对的;对一整类根本没有 GitHub 路由的构建环境是错的——cloud 把 framework COPY 进 buildx 阶段再构建、气隙构建、fork、按 tag 复现历史版本。这些树是不可变的、已经合并过的,闸门要问的那句"我这个 commit 相对 main 删了什么"在那里没有主语,却照样把整个构建打红。

方案

新增提交进树的 packages/spec/authorable-surface.base.json:

{ "description": "", "baseRev": "88b9b2d…(40 hex)", "keys": ["ai/Agent:model", ""] }

keys 就是 authorable-surface.jsonbaseRev(一个 origin/main 上的 commit)时的内容。

防旁路性质为什么还在

想靠改锚点作弊,只有两条路,都被上面那两条校验堵死:

  1. 从锚点里删一行 → keys 不再等于 baseRev 那个 commit 的基线 → 红。要让它不红,得让一个已经合并的上游 commit 少掉那一行,而 PR 做不到这件事。
  2. baseRev 指到自己分支上的 commit(那个 commit 的基线确实被改过) → 祖先校验红:它不是 origin/main 的祖先。

再加一条写入侧的约束:锚点只能由 git 解析出来的基线写(gitResolvedAnchor),离线构建永远不写它——否则离线构建就能把锚点推进到"自己现在的样子",等于自己给自己做证。

⛔ 没有 SKIP=1 之类的环境变量。两种锚点都拿不到时,构建仍然 exit 1(原来那条 test 改写成钉这一半)。

刷新故事 / 怎么防漂移

  • 谁写:gen:schema(写模式)。它在删除闸门判完之后才写——顺序是有意的:一次因为"没证明的删除"而退出的运行根本走不到写入那一行,所以锚点永远不可能被推过一个它没有放行的删除。
  • 写什么:merge base 的基线,不是本分支的现状。
  • 什么时候变:只有当 keys 真的变了才重写(baseRev 单独变化不触发),所以它的 churn 频率 = authorable-surface.json 自己的 churn 频率,不是每个 PR。
  • 滞后是合法的,不是错误:在 main 上 merge base 就是 HEAD,所以这个文件必然比自己的 surface 落后一个 PR。因此 --check 只证明它真实(authentic),不要求它最新(current);落后时打一行 ℹ️ 提示,由下一次 gen:schema 顺手补上。这一点在代码注释和 regen-artifacts.mjs 的条目里都写清楚了,免得下一个人把它"修"成 fatal 而在 main 上自伤。

归属与配套

  • os-regen 合并驱动:已加(.gitattributes + scripts/regen-artifacts.mjs,pnpm check:merge-driver 双向自检通过)。理由:同一个生成器、同样的 8k 行有序数组、同样的冲突形状,解法永远是"按合并后的树重算",不是文本合并。条目注释里注明了它与邻居的不同点:陈旧不算错。
  • packages/spec/package.jsonfiles 不动:authorable-surface.json 本来就不在发布清单里(闸门跑在仓库树上,不在 npm tarball 里),锚点跟它保持一致。
  • 未改任何 schema 源与其它生成物语义;未碰 content/docs/releases/

验证证据

1. RED(修之前,复刻消费者构建形状) — 在没有可解析 git 的环境里跑 spec 构建:

GIT_DIR=…/absent.git tsx scripts/build-schemas.ts --check
❌ Cannot resolve origin/main to anchor the authorable-surface deletion check (#4650).
EXIT=1

与 issue 正文里 cloud 那条报错逐字一致。

2. GREEN(同一条命令,修之后):

ℹ️  origin/main is not resolvable in this build environment — anchoring the authorable-surface
   deletion check (#4650) on the committed authorable-surface.base.json: authorable-surface.json
   as of 88b9b2d5cc8d, verified upstream when it landed (#5235).
✅ Successfully generated 1655 schemas.
EXIT=0

3. 开发检出仍然绿:pnpm --filter @objectstack/spec check:authorable-surface → exit 0。

4. 反篡改(在真实开发检出里手改锚点,删掉一行) → 红:

❌ authorable-surface.base.json is not the baseline it claims to be (#4650, #5235):
   it says it mirrors authorable-surface.json at 88b9b2d5cc8d, but 1 line(s) are missing
   from it and 0 line(s) are not in that commit at all.
     - data/Object:label  (at 88b9b2d5cc8d, absent here)

5. 测试:packages/spec/scripts/build-schemas-check-mode.test.tsTests 18 passed (18);pnpm --filter @objectstack/spec typecheck → exit 0;pnpm check:merge-driver → 全绿。

新增 6 条(沿用该文件已有的 sandbox:真 git 仓 + 伪造的 refs/remotes/origin/main,删掉这个 ref 就是消费者构建的忠实模型):离线能构建、离线仍然会红(锚点记着而本次构建产不出的 key)、离线写模式不推进锚点(成功和失败两条路都钉了)、手改锚点被抓、baseRev 指向本地 commit 被抓、锚点缺失时 --check 只报不写而 gen:schema 会生成。

原有那条 "fails LOUDLY when origin/main cannot be resolved" 按新契约改写:它钉的性质(没有任何锚点时不许静默跳过)完好,只是触发条件从"没有 origin/main"收窄成"两种锚点都没有"。

下游

cloud 的 pin bump(cloud#1012 / cloud PR #1091)在这条合并后解锁。

另外一条实测到的下游收益:cloud 已经用"把 .git 放进构建上下文 + 在 builder 里 git update-ref refs/remotes/origin/main HEAD"绕过这个闸门(cloud#1098 / cloud PR #1106),代价写在它自己的 dockerignore 注释里——COPY objectstack/ 那层每次都变、缓存全失效、每次部署冷构建约 20 分钟,并把"买回缓存"记成了 cloud#1102。这条合并后那个绕法可以撤掉:锚点在树里,不需要 .git,也不需要那个 update-ref(顺带一提,它把 origin/main 指到 HEAD,等于让闸门拿自己比自己——树内锚点比它严格)。cloud 侧的清理不在本 PR 范围内。


🤖 Generated with Claude Code

https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB


Generated by Claude Code

…eeds no network (#5235)

`gen:schema` resolved the authorable-surface deletion baseline only out of git —
`origin/main`, with a self-heal fetch — and exited 1 when neither worked. That is
right for a developer who forgot to fetch and wrong for every build environment
with no route to GitHub: image-build stages that COPY a SHA-pinned framework tree
into a container, air-gapped builds, forks, historical-tag reproductions. Those
trees are immutable and already merged, so the question the gate asks ("what did
this commit delete relative to main?") has no subject there — yet it failed them.

The baseline is now also committed as `packages/spec/authorable-surface.base.json`:
the keys of `authorable-surface.json` at `baseRev`, a commit on origin/main.

- origin/main reachable: unchanged. The gate anchors on the merge base exactly as
  before, and additionally verifies the committed anchor against it — `baseRev`
  must be an ancestor of origin/main and its keys must BE that commit's baseline.
  So the environments that can police the anchor do, and a commit cannot edit it
  to hide a deletion.
- origin/main unreachable: the gate anchors on the committed file and the build
  proceeds. It still runs — a recorded key this build no longer emits is as fatal
  as before — and only a git-resolved baseline may write the file, so an offline
  build can never advance the anchor to its own state.

No env-var skip: a deletion check that can be switched off is the bypass #4650
closes. With neither anchor available the build still exits 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 8:00pm

Request Review

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

CI 红诊断(PM,三个失败 job 同一签名,非 flaky)

Build Core / Dogfood 1-3 全部死在 @objectstack/spec:build:

(shallow history — using origin/main tip de113a4caf51 as the baseline anchor)
❌ authorable-surface.base.json names a baseRev (88b9b2d5cc8d) that is NOT an ancestor of origin/main (#5235).

根因:祖先校验不是 shallow-safe。锚点写入时 merge base 为 88b9b2d,CI 时 main 已到 de113a4 —— 全历史下 88b9b2d 是真祖先(陈旧合法,本 PR 设计明文允许),但 CI 浅检出里祖先链不可走,merge-base --is-ancestor不可判定返回成了。sandbox 测试全用完整仓,测不到这个形状;而闸门对 baseline 本身早有浅历史回退先例(日志首行的 shallow history — using origin/main tip 就是),新增的真实性校验没有跟上。

返工方向(细则由 dev 定,性质不能变):

  1. 真实性校验改为只在祖先关系被积极证伪时才红;浅历史下不可判定时:先尝试定向补深(git fetch origin <baseRev> / 递进 --deepen),仍不可判定则降级为 ℹ️ 放行 —— 此时本次构建的删除闸门本来就锚在 git 解析的 merge base 上,树内锚点在该环境不承重,防旁路性质不受损(写入侧「只准 git 解析基线写」+ 完整检出下的校验仍然把守);
  2. 新增一条 sandbox 测试,忠实建模 CI 浅检出形状(shallow clone + origin/main ref 在但历史截断 + 锚点 baseRev 合法但不可达),钉住「陈旧合法在浅检出下不红」;
  3. 反篡改两条测试(改 keys、baseRev 指本地 commit)在完整仓形状下保持红。

dev 报告到达后按 REWORK 重派,本条即返工简报。


Generated by Claude Code

…5235)

First CI run on this branch failed every job: `merge-base --is-ancestor` answers
"not an ancestor" in a depth-1 checkout, because the anchor's commit is fetched
as its own shallow root and there is no history to walk between it and the tip.
The ancestry half of the anchor verification therefore rejected a baseRev that
demonstrably IS on main, exactly the way the merge-base fallback a few lines
above already anticipates for the baseline itself.

Ancestry is now judged only where it can be: `rev-parse --is-shallow-repository`
gates it, and a shallow run says so and verifies the recorded KEYS alone — the
half truncation cannot take away, since the fetched commit's tree is present.
A full clone (every dev checkout — where the anchor is regenerated, and where a
hand-edit is therefore caught) still checks both.

Two tests pin it, using `$GIT_DIR/shallow` to truncate the sandbox repo exactly
as `--depth=1` does: a lagging-but-authentic anchor stays green, and a shed line
still goes red there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

Copy link
Copy Markdown
Contributor Author

补充:第一轮 CI 抓到的一个真问题(已修,e80df68)

第一轮 CI 全线红,原因不是闸门本身,而是我给锚点加的祖先校验在浅检出里判错:

   (shallow history — using origin/main tip de113a4caf51 as the baseline anchor)

❌ authorable-surface.base.json names a baseRev (88b9b2d5cc8d) that is NOT an ancestor of
   origin/main (#5235).

88b9b2d 确实在 main 上。问题是 CI 是 depth=1 检出:我按 SHA 补 fetch 回来的那个 commit 会被嫁接成它自己的浅根,tip 也是浅的,两者之间没有可走的历史,于是 merge-base --is-ancestor 回答"不是祖先"。这和这段代码上面几行早就写下的那条注记是同一件事——"In a shallow clone there is no walkable ancestry"——我在新加的校验里把它漏了。

修法:祖先只在能走历史的地方判(git rev-parse --is-shallow-repository 把关);浅检出直说一句、只校验记录的 keys——那一半不受截断影响(按 SHA fetch 会把那个 commit 的整棵树带回来,git show 读得到)。完整克隆(每个开发检出,也就是锚点被重新生成、因而手改会被抓的那个地方)两条都查。

两条新测试钉住:用 $GIT_DIR/shallow 把 sandbox 仓库按 --depth=1 的方式截断(实测 is-shallow-repository 变 true、--is-ancestor 变 false、git show BASEREV:file 仍然可读),然后

  • 滞后但真实的锚点 → 绿,且不出现 "NOT an ancestor of";
  • 同样浅的情况下从锚点里删一行 → 仍然红。

Tests 20 passed (20)(原 12 + #5235 的 6 + 浅检出 2),typecheck exit 0。

这条也顺带说明了树内锚点的定位:在浅检出里能证的是"锚点没被改",完整克隆里才连"它指向的 commit 属于上游"一起证;而 #4650 的实体判定在这两种环境里走的都还是 merge base,没有变。


Generated by Claude Code


Generated by Claude Code

claude added 2 commits August 4, 2026 19:44
…aseline (#5235)

`git merge origin/main` brought #5289 / #5293 / #5296 / #5300, three of which
change the authorable surface (theme token tombstones, the HttpServerConfig
removal, the NotificationAction / EmbedConfig removals). The anchor is written
from the merge base, so it moves with it: baseRev 88b9b2d26e1029, 8045 → 8016
keys, regenerated by `gen:schema` rather than text-merged — this file is on the
os-regen list precisely because a textual merge of it means nothing.

Verified line-for-line against `git show 26e1029:packages/spec/authorable-surface.json`,
and the siblings' entries survived in it: `ui/Theme:animation [RETIRED]` /
`ui/Theme:zIndex [RETIRED]` are carried, HttpServerConfig / NotificationAction /
EmbedConfig are gone from every generated witness at once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

Copy link
Copy Markdown
Contributor Author

已同步 main 并整体重生成(2adc419,入队前)

git merge origin/main(26e1029,含 #5289 / #5293 / #5296 / #5300)——无冲突,os-regen 驱动没有留下 pending 标记;随后把全部 os-regen 路径 git checkout origin/main -- 回主线版本(唯独 authorable-surface.base.json 跳过:它是本 PR 新建的,main 上没有),再按清单整体重生成,没有对任何生成文件做文本合并。

锚点按设计随 merge base 前移:baseRev 88b9b2d26e1029,8045 → 8016 keys(正好是三个兄弟 PR 的退役/删除)。这一步由 gen:schema 写,不是手改。

断言

(a) 兄弟 PR 的条目都在

PR 证据
#5289 主题 token ui/Theme:animation [RETIRED] / ui/Theme:zIndex [RETIRED] 在 surface 和锚点里都在;conversions registry 里 9 个 token 面(theme.typography.fontSize / … / theme.animation / theme.zIndex)在;spec-changes.jsontheme.animationtheme.zIndex
#5293 HttpServerConfig 九个键从 authorable-surface.json / api-surface.json / json-schema.manifest.json 同时消失(锚点同步)
#5300 NotificationAction / EmbedConfig 同样三处witness 归零;migrations registry 的 D3 链条目 ui-notification-action-embed-config-retired 在;spec-changes.json 含两者

顺带记一句免得下个人误判:#5293 没有 D2/D3 条目是对的,不是合并丢的——它的变更集写明"零 reader 且没有任何作者面入口",没有作者可迁移,按 ADR-0087 只由 manifest ratchet 与 check:api-surface 裁定。

(b) 锚点机制完好

anchor.baseRev              = 26e1029f5cdf7f63719e3f58eb661b19f65b78fe
merge-base(HEAD,origin/main)= 26e1029f5cdf7f63719e3f58eb661b19f65b78fe
anchor.keys === surface@26e1029(逐行): true ( 8016 keys )

build-schemas-check-mode.test.tsTests 20 passed (20)(针对 baseRev 重跑)。

(c) 布线没被合并吃掉:.gitattributes:40regen-artifacts.mjs:34 的锚点条目都在,pnpm check:merge-driver✓ .gitattributes ↔ regen-artifacts.mjs agree on 9 path(s) 全绿。

typecheck exit 0;spec 全量 Test Files 309 passed (309) / Tests 7957 passed (7957);9 张生成物门 ✓ All 9 generated artifacts are up to date.;7 张源审计门(liveness / empty-state / react-declaration-parity / skill-examples / variant-docs / exported-any / dual-source-exports)逐条 exit 0;check:nul-bytes OK。远端 CI 在 2adc41924 项全部 success/skipped(含 Build Core 与 TypeScript Type Check —— 后者就是在真实浅检出形状里跑 check:authorable-surface 的那条)。


Generated by Claude Code


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 04fab5e Aug 4, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5235-offline-surface-anchor branch August 4, 2026 20:28
os-zhuang pushed a commit that referenced this pull request Aug 4, 2026
)

Serial-landing sync for PR #5306. `git merge origin/main`, then all NINE
os-regen paths reset to origin/main and regenerated wholesale from the merged
sources (the path list read from the merged tree's `.gitattributes`, not from
memory — #5304 added `authorable-surface.base.json` as the ninth).

The merge driver had deferred `api-surface.json` and `json-schema.manifest.json`,
and git's textual result RESURRECTED symbols three sibling PRs had retired —
this branch's pre-merge copies still listed them. Wholesale regeneration removes
them again:

- #5293: HttpServerConfig / HttpServerConfigInput / HttpServerConfigSchema
- #5289: Animation / AnimationSchema / ZIndex / ZIndexSchema
- #5300: EmbedConfig / EmbedConfigSchema / NotificationAction /
  NotificationActionSchema

Verified after regeneration: this PR's 7 exports and `data/FilterArray` still
present, the FilterArray docs section still carries its describe text, all three
siblings' retirements absent from every witness, the #5304 anchor authentic
(baseRev an ancestor of origin/main, keys identical line-for-line to that
commit's surface), and `check:merge-driver` reconciling 9 paths both ways.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
os-zhuang pushed a commit that referenced this pull request Aug 4, 2026
Second relay merge: #5300 / #5304 / #5306 / #5308 / #5318 / #5326 / #5327.
Textually clean, but the os-regen driver defers generated artifacts rather than
text-merging them, so `json-schema.manifest.json` again came out holding this
branch's pre-merge side — this time still listing `ui/EmbedConfig` and
`ui/NotificationAction`, both retired by #5300. Reset the deferred artifacts to
`origin/main`, rebuilt from the merged tree, regenerated wholesale.

Post-regen assertions (a silent one-side drop is exactly what this catches):
api-surface delta vs `origin/main` is exactly this PR's four additions and ZERO
removals; manifest delta is one addition (`ui/ViewItemWire`) and zero removals;
every sibling retirement stays removed (`ui/EmbedConfig`, `ui/NotificationAction`,
`system/HttpServerConfig`, `ui/Animation`, `ui/ZIndex`) and every sibling
addition stays present (`FilterArray` ×7, `EmailProvider` ×2).
`check:authorable-surface` (+ its #5304 `.base.json` anchor) is green and the
anchor file is byte-identical to `origin/main` — not hand-edited.

`metadata-form-zod-reconciliation.test.ts` co-edited with #5280/#5318 and merged
SEMANTICALLY, not by taking a side: #5318 rewrote the docblock, imports, helpers
and test bodies, while this PR's only edit is `unwrap`'s `pipe` case, so the two
did not overlap textually — but they do interact, and in the direction that
matters. #5318's `isRetiredAt` / `authorableKeysOf` both route through
`unwrap`/`keysOf`, and `view`'s root is now a `z.preprocess` pipe. Measured both
ways: without this PR's #4488-style fix `unwrap(view root)` resolves to
`transform` and `keysOf` returns NULL, so #5318's brand-new tombstone assertions
would be VACUOUS on `view` (and the pre-existing key-bearing assertion would
fail outright); with it, 89 keys. Both PRs' assertions are live on every type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
os-zhuang added a commit that referenced this pull request Aug 4, 2026
三处冲突全部语义合并,无机械取边:

- `ui/index.ts` barrel:保留 #5300 为 notification.zod / sharing.zod 写的两段
  「幸存导出」说明,同时落实本单五个退役模块的 export 删除。
- `migrations/registry.ts`:step17 rationale 与 semantic[] 两处冲突都是**双方各自
  追加**,两边全留。rationale 里把 #5015 的段落排在前、本单排在后,并把本单开头
  从「最后一个不需要改源码的退役」改写为「同类的第二个」—— #5015 落地后那句
  独占措辞已不成立。semantic[] 两个条目并存。
- 台账:双方各删各的行,**两边的删除都生效**(#5300 删 sharing/notification 两行,
  本单删五行交互行);`no door` 小结改写为同时反映两次退役,并记下这一类九个条目
  里已有七个在同一个发布窗口内由「退役」而非「收紧」闭合。

按 AGENTS.md §9 刷新构建状态(frozen-lockfile install + 重建),再按 os-regen 四步
把九条生成路径整体重算。⛔ 未手改 #5304 新引入的 authorable-surface.base.json
锚点 —— 该文件只由 gen:schema 从 git 解析出的基线写入。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
os-zhuang added a commit that referenced this pull request Aug 4, 2026
#5304 引入的树内锚点。keys 因兄弟 PR 落地而漂移,gen:schema 据此把 baseRev 从
26e1029(#5300)推进到当前 merge base 1c3da1f,并整体重写 keys。

⛔ 未手改一个字节:该文件只由 gen:schema 从 git 解析出的基线写入,手改它就是
#4650 要防的那种攻击本身。authenticity 两条均已核:baseRev 是 origin/main 的
祖先(此处即等于 origin/main),且 keys 与该 commit 的 surface 逐行一致 ——
以 check:authorable-surface 转绿为准,而不是要求 baseRev == 任何特定值。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…objectstack-ai#5306)

* feat(spec): declare FilterArray as input-only authoring sugar (objectstack-ai#5285)

`FilterArray` was a name with no definition. Three READMEs, `llms.txt`, four
skills, the query-adapter docs and this package's own react-blocks prop table
all taught authors to write it, while the protocol never declared it anywhere.

Declare it in `data/filter.zod.ts`, beside the operator vocabulary it is built
from and the sink it lowers through:

- `FilterArray` + `FilterArrayComparison` / `FilterArrayGroup` /
  `FilterArrayList` — the three shapes the measured producers emit
- `FilterArraySchema` — the Zod authoring gate
- `FilterArrayOperator` — canonical spellings, derived from `AST_OPERATOR_MAP`
  rather than restated, so it cannot drift from the lowering (objectstack-ai#3948)
- `FILTER_ARRAY_LOGIC_KEYWORDS` / `FilterArrayLogicKeyword`

Input-only: lowered to a `FilterCondition` at the single sink `parseFilterAST`
on arrival. The storage/wire contract is unchanged — a query's `where` is a
`FilterCondition` and deliberately does NOT accept the array dialect, pinned as
a negative test so a future widening of the protocol face fails loudly.

`AST_OPERATOR_MAP` keeps its literal key set via `satisfies` so the operator
type can be derived; the two runtime lookups go through one `astOperatorLowering`
helper. Behaviour identical.

Step 1 of objectstack-ai#5158's ruling C. No engine or driver changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

* chore(spec): regenerate os-regen artifacts against main @ 04fab5e (objectstack-ai#5285)

Serial-landing sync for PR objectstack-ai#5306. `git merge origin/main`, then all NINE
os-regen paths reset to origin/main and regenerated wholesale from the merged
sources (the path list read from the merged tree's `.gitattributes`, not from
memory — objectstack-ai#5304 added `authorable-surface.base.json` as the ninth).

The merge driver had deferred `api-surface.json` and `json-schema.manifest.json`,
and git's textual result RESURRECTED symbols three sibling PRs had retired —
this branch's pre-merge copies still listed them. Wholesale regeneration removes
them again:

- objectstack-ai#5293: HttpServerConfig / HttpServerConfigInput / HttpServerConfigSchema
- objectstack-ai#5289: Animation / AnimationSchema / ZIndex / ZIndexSchema
- objectstack-ai#5300: EmbedConfig / EmbedConfigSchema / NotificationAction /
  NotificationActionSchema

Verified after regeneration: this PR's 7 exports and `data/FilterArray` still
present, the FilterArray docs section still carries its describe text, all three
siblings' retirements absent from every witness, the objectstack-ai#5304 anchor authentic
(baseRev an ancestor of origin/main, keys identical line-for-line to that
commit's surface), and `check:merge-driver` reconciling 9 paths both ways.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…ack-ai#5074) (objectstack-ai#5319)

* feat(spec)!: split ViewItemSchema into an authoring gate and a wire variant (objectstack-ai#5074)

`ViewItemSchema` carried two contracts at once: the authoring surface
`defineViewItem()` and Studio's view-create form parse, AND member 1 of the
`ViewMetadataSchema` union that `saveMetaItem` validates every persisted `view`
body against. The wire role needed Studio's round-trip keys through, so the
shape stayed open — and `defineViewItem({ …, confg: {…} })` parsed clean,
handing back a ViewItem with no view configuration at all.

Per the maintainer's ruling (option A, 2026-08-04):

- `ViewItemSchema` is strict on both arms — the authoring gate.
- `ViewItemWireSchema` is the `.strip()` wire variant and is member 1 of
  `ViewMetadataSchema`, with `isPinned` / `sortOrder` DECLARED on it instead of
  surviving because nobody closed the member. Both are built from one
  `viewItemArmShape()`, so the two postures cannot drift into two
  transcriptions (a `discriminatedUnion` cannot be `.extend()`ed).

The scope addendum's hard requirement was recursive-effective openness, which a
posture flip cannot deliver: `.strip()` re-opens a member's TOP level only, so
the console-decorated NESTED blocks were still reached at full strictness.
`stripViewConsoleDecorations` (+ the declared `VIEW_CONSOLE_ROW_DECORATIONS`
vocabulary) removes them at the wire door before the union runs — the
write-path mirror of `stripReadDecorations`. That let both blocked sites close:

- `ViewFilterRuleSchema` — objectstack-ai#5114's hotfix was explicitly provisional; retired.
- `ListView.sort[]` — 批 18's revert (objectstack-ai#5070); the `direction → order` alias
  (objectstack-ai#4721, a silently REVERSED sort) comes back with it.

`id` is still not declared anywhere: it is a React list key, and declaring it
would teach an AI author to emit a UUID (批 18 Q1, rejected on record).

Two gate walkers went silent on `view` under a preprocess-rooted registration —
the exact blind spot objectstack-ai#4488 already fixed in `check-liveness.mts` — and are
fixed the same way here. A gate that stops covering a type is worse than one
that fails.

Fixes objectstack-ai#5074

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

* chore(spec): regenerate baselines after merging origin/main (objectstack-ai#5074)

The os-regen merge driver defers generated artifacts rather than text-merging
them, so `json-schema.manifest.json` came out of the merge holding this
branch's pre-merge side — which still listed `system/HttpServerConfig`,
`ui/Animation` and `ui/ZIndex`, all retired by the objectstack-ai#5289/objectstack-ai#5293 chain. Reset the
deferred artifacts to `origin/main`, rebuilt, and regenerated wholesale.

Asserted after regenerating, because a silent one-side drop is exactly what
this step exists to catch: the api-surface delta vs `origin/main` is exactly
this PR's four additions and ZERO removals; the manifest delta is one addition
(`ui/ViewItemWire`) and zero removals; the sibling retirements
(`HttpServerConfigSchema`, `ui/Animation`, `ui/ZIndex`) all stay removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

* chore(spec): regenerate baselines after merging origin/main (spec 车道接力)

Second relay merge: objectstack-ai#5300 / objectstack-ai#5304 / objectstack-ai#5306 / objectstack-ai#5308 / objectstack-ai#5318 / objectstack-ai#5326 / objectstack-ai#5327.
Textually clean, but the os-regen driver defers generated artifacts rather than
text-merging them, so `json-schema.manifest.json` again came out holding this
branch's pre-merge side — this time still listing `ui/EmbedConfig` and
`ui/NotificationAction`, both retired by objectstack-ai#5300. Reset the deferred artifacts to
`origin/main`, rebuilt from the merged tree, regenerated wholesale.

Post-regen assertions (a silent one-side drop is exactly what this catches):
api-surface delta vs `origin/main` is exactly this PR's four additions and ZERO
removals; manifest delta is one addition (`ui/ViewItemWire`) and zero removals;
every sibling retirement stays removed (`ui/EmbedConfig`, `ui/NotificationAction`,
`system/HttpServerConfig`, `ui/Animation`, `ui/ZIndex`) and every sibling
addition stays present (`FilterArray` ×7, `EmailProvider` ×2).
`check:authorable-surface` (+ its objectstack-ai#5304 `.base.json` anchor) is green and the
anchor file is byte-identical to `origin/main` — not hand-edited.

`metadata-form-zod-reconciliation.test.ts` co-edited with objectstack-ai#5280/objectstack-ai#5318 and merged
SEMANTICALLY, not by taking a side: objectstack-ai#5318 rewrote the docblock, imports, helpers
and test bodies, while this PR's only edit is `unwrap`'s `pipe` case, so the two
did not overlap textually — but they do interact, and in the direction that
matters. objectstack-ai#5318's `isRetiredAt` / `authorableKeysOf` both route through
`unwrap`/`keysOf`, and `view`'s root is now a `z.preprocess` pipe. Measured both
ways: without this PR's objectstack-ai#4488-style fix `unwrap(view root)` resolves to
`transform` and `keysOf` returns NULL, so objectstack-ai#5318's brand-new tombstone assertions
would be VACUOUS on `view` (and the pre-existing key-bearing assertion would
fail outright); with it, 89 keys. Both PRs' assertions are live on every type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…fline (objectstack-ai#4988) (objectstack-ai#5321)

* feat(spec)!: 退役 ui/ 五个没有承载键的交互配置文件 (objectstack-ai#4988)

touch / dnd / keyboard / animation / offline 五个模块声明了完整的交互配置词表
(22 个 z.object 站点、32 个 def、64 个导出名),而协议里**没有任何承载键** ——
没有 schema 声明过 `touch:` / `dnd:` / `keyboard:` / `animation:` / `offline:`
槽位,所以没有元数据文档能到达这些形状,也从来没有 parse。

三条独立测量在 origin/main 上重跑,每条的阳性对照都在同一次运行内通过:
静态(除 ui/index.ts barrel 外零 importer)、图可达(25 roots / 4742 nodes,
21 个具名对象形状全不可达,Page/Webhook/StateMachine 全部 direct,注入合成
承载键后 21 个全部翻转)、调用点(三仓零 parse)。

真正的缺陷在**文档侧**:authorable-surface.json 躺着 109 个键,
content/docs/references/ui/{touch,dnd,keyboard,animation,offline}.mdx 把它们
渲染成授权表 —— AI 作者照 dnd.mdx 往页面组件写 dnd: 块,会被
PageComponentSchema 以未知键拒绝。这是 PD#10 的宣传运行时不提供的能力,
不是 strictness 缺口:收紧只会精确校验一个没人够得到的槽位(objectstack-ai#4583)。

按 spec-property-retirement 路线 3(nothing parses it → 既不墓碑也不 D2),
与 objectstack-ai#4834 / objectstack-ai#4938 同形;注册 ADR-0087 D3 SemanticMigration
`ui-interaction-config-family-retired`,并扩写 protocol-17 步骤的 rationale。

⚠️ ui/animation.zod.ts 与 objectstack-ai#5021 退役的 theme animation 块是两张面:不同文件、
不同 def、不同 manifest 条目。那一张有承载键、走墓碑;这一张没有、走删除。

Fixes objectstack-ai#4988

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

* chore(spec): 末段同步 —— os-regen 基线整体重算于 origin/main (objectstack-ai#5293 已并入)

按 .gitattributes 的 os-regen 四步互保执行:merge origin/main → 把全部
os-regen 路径 checkout 回 origin/main → 整体重算 → 断言兄弟条目仍在。

互保断言(重算后的基线上实测):
- 本单 objectstack-ai#4988:32 个 ui/ 交互 def 不在 manifest,109 个 authorable 键为 0;
- 兄弟 objectstack-ai#4938 / PR objectstack-ai#5293:system/HttpServerConfig 三张表全部仍为删除态;
- 兄弟 objectstack-ai#5021 / PR objectstack-ai#5289:6 个 theme 族 [RETIRED] 标记全部仍在
  (ui/Theme:animation、ui/Theme:zIndex、ui/Typography:fontSize/fontWeight/
  letterSpacing/lineHeight)—— 与本单删除的 ui/ComponentAnimation 一族是
  两张互不相干的面,名字撞车但条目分离,已逐条核对;
- 阳性对照:ui/Page 的 authorable 键仍在,ui/ResponsiveConfig 仍被 emit。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

* chore(spec): gen:schema 推进 authorable-surface 锚点至 1c3da1f

objectstack-ai#5304 引入的树内锚点。keys 因兄弟 PR 落地而漂移,gen:schema 据此把 baseRev 从
26e1029(objectstack-ai#5300)推进到当前 merge base 1c3da1f,并整体重写 keys。

⛔ 未手改一个字节:该文件只由 gen:schema 从 git 解析出的基线写入,手改它就是
objectstack-ai#4650 要防的那种攻击本身。authenticity 两条均已核:baseRev 是 origin/main 的
祖先(此处即等于 origin/main),且 keys 与该 commit 的 surface 逐行一致 ——
以 check:authorable-surface 转绿为准,而不是要求 baseRev == 任何特定值。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…摆纠偏、飞行中重叠、预期红停放 (objectstack-ai#5441) (objectstack-ai#5501)

* docs(pm-dispatch,os-dev): 串行接力一夜沉淀的六条缺口补进 SKILL —— 接力模式、锚点措辞、裁决传播扫描、停摆纠偏、飞行中重叠、预期红停放 (objectstack-ai#5441)

2026-08-04/05 夜 spec 车道以串行接力连落 10 个 PR(objectstack-ai#5304objectstack-ai#5365),六个情形是
现行 SKILL 没有覆盖、靠现场即兴的,各有实付学费。按 issue 注明的落点章节逐条插入:

- 第 7 步「入队与落地」新增平行小节「串行接力」:每棒一整圈(auto-merge 由 PM 挂、
  dev 永不碰,ready 与 auto-merge 顺序不可反且每棒各走一次)、相邻棒同文件交接语义
  而非文本(objectstack-ai#5318/objectstack-ai#5319 实例)、两棒散文互锁由 PM 指派分工(objectstack-ai#5323objectstack-ai#5365objectstack-ai#5335)。
- 「入队与落地 A」新增锚点断言措辞:authenticity = baseRev 是 origin/main 祖先 且
  keys 与该 commit 逐行一致;baseRev 允许滞后;⛔ 不得要求 baseRev == merge-base
  (那会教唆手改锚点,即 objectstack-ai#4650 攻击自身)。四步序的第 3 步补上「先 commit merge」,
  并引用 objectstack-ai#5370 / objectstack-ai#5371 两个新陷阱(仅引用,不实现)。
- 第 5 步派发词 + 第 7 步 review 新增「裁决传播 = 全仓 pin 扫描」:翻 pin 一轮翻完
  且必须保留承重(objectstack-ai#5322 裁决、objectstack-ai#5365 的 REST 层漏翻)。
- 第 6 步 Collect 的 subagent 半边新增停摆纠偏:watcher 永不触发,中途状态即停摆
  信号,第三次视为不可靠改走接手协议。生产端半边同步落到 os-dev.md 资源纪律第 6 条。
- 第 5 步 same-day churn 新增姊妹段「飞行中范围重叠」:每轮读 origin/main 时对每个
  在飞 dispatch 做相交判断,相交即预警(objectstack-ai#5322 × objectstack-ai#5335 实例)。
- 「入队与落地 B」新增依赖 PR 的预期红停放:draft 停放 + 签名级预期红清单 + 解除
  条件,新签名才是真问题(objectstack-ai#5365 的 REST 红即由此识别)。

仅改 `.claude/` 内部 agent 协议文本,不发布任何包;六条落点之外未动任何段落。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE

* docs(pm-dispatch): 停摆纠偏第三条的交叉引用改指「step 5」—— 接手协议在第 5 步,不在第 6 步之下

自查发现的方向错误:「Handing off an interrupted dev」小节是第 5 步的子节
(SKILL.md:839),而新增段落写在第 6 步(:923),原文写「below」会把读者指向
第 6 步之后的 Cloud mode 段。同段里对 cloud-mode ~2h 阈值的「below」是对的,保留。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE

* docs(pm-dispatch): 飞行中重叠一段的时间戳改用可核验值 —— objectstack-ai#5335 的 merged_at 是 23:49:44Z,不是 00:0xZ

issue objectstack-ai#5441 正文写「objectstack-ai#5335 在 00:0xZ 合入」,核 GitHub API 的 merged_at 与 main 上
squash 提交的 committer date,两者一致给出 2026-08-04T23:49:44Z。改写成「起飞后
32 分钟合入(merged_at 2026-08-04T23:49:44Z)」,把不可核验的钟点换成可核验的
时间差 + 权威字段;起飞时刻 23:17Z 沿用 issue 的记述(无仓内产物可核,且非承重)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants