feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase) - #1018
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)#1018typefield wants to merge 26 commits into
Conversation
…phase) Framework-only: adds the reviewed wait contract mirroring the DryRunSpec pattern (types declaration -> ContractDecl -> ContractFinal -> ToolSpec -> Schema wait key). No business command declares it yet. - contract.WaitSpec (mode poll/event/auto, poll_command, status_query, terminal status->success/failure map, pending_values, event_key, match_field, default_timeout_secs) with closed-set Validate - Spec.WaitPoll hook pairs with the declaration at construction time (declared without hook / hook without declaration both panic) - declared leaves register --wait / --wait-timeout natively (never FlagSpec, so they cannot enter MCP toolArgs); undeclared leaves reject the flags as unknown instead of ignoring them - internal/wait engine: immediate-first-poll, x1.5 backoff capped 30s, dotted status extraction, fail-closed on unknown status, timeout -> pending - ResultInvoke path closes the unified envelope: success terminal -> success, failure terminal -> failure with new wire-stable error.type "wait" (exit code 8, additive like partial=7), timeout -> pending + meta.operation.timed_out with last observed state (exit 0) - output.WithOutcome / WithErrorInfo / WithOperationTimedOut preserve envelope invariants (I2/I3, pending requires meta.operation) Verified: go test ./... green; check-generated-drift.sh ok (schema assembly deterministic, wire unchanged); check-schema-catalog.sh ok (27 products, 1121 tools).
wxianfeng
left a comment
There was a problem hiding this comment.
开源 DWS Codex 自动 CR:不通过,存在代码冲突
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
- 评审 SHA:
b49bc0ed1403cdd10ad9c9a26e7acf093cb7fb1f - CI 状态:未通过(26 通过,4 跳过,1 未通过)
- 冲突状态:有冲突
- Agent 测试报告:不涉及
- 指令 CI 集成测试:不涉及
- 自动 CR 结论:不通过,存在代码冲突
- 代码评审结论:不通过,需修复
变更总结
本次改动新增 Wait 契约、Schema 投影、轮询引擎以及统一结果的终态转换,整体分层清晰,但当前实现存在三项阻塞性语义缺陷:event/auto 模式没有对应执行路径,超时可能被误报为轮询失败,非统一结果命令的失败终态仍以成功退出。由于这些行为与公开的 Wait 契约和变更说明直接冲突,当前版本不应合并。未执行测试或仓库代码,结论仅基于静态审查。
CI 单测结果
- ✅ AI Behavior — 通过
- ✅ CLI Smoke — 通过
- ❌ Coverage — 失败
- ✅ Coverage (baseline) — 通过
- ✅ Coverage (current) — 跳过
- ✅ Coverage (current: app) — 通过
- ✅ Coverage (current: cli) — 通过
- ✅ Coverage (current: generators) — 通过
- ✅ Coverage (current: helpers) — 通过
- ✅ Coverage (current: remaining) — 通过
- ✅ Coverage (macOS) — 跳过
- ✅ Coverage (supporting) — 通过
- ✅ Coverage (Windows) — 跳过
- ✅ Edition — 通过
- ✅ Interface Integrity — 通过
- ✅ Lint — 通过
- ✅ Mock MCP — 通过
- ✅ Policy — 通过
- ✅ route — 通过
- ✅ Test — 通过
- ✅ Test (changed packages) — 跳过
- ✅ Test (cross-platform compile) — 通过
- ✅ Test (macOS auth/keychain) — 通过
- ✅ Test (race: app) — 通过
- ✅ Test (race: cli) — 通过
- ✅ Test (race: generators) — 通过
- ✅ Test (race: helpers) — 通过
- ✅ Test (race: remaining) — 通过
- ✅ Test (race: smoke) — 通过
- ✅ Test (Windows) — 通过
- ✅ Test (workflow and release contracts) — 通过
结论说明
Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。
代码问题
-
[P1 | 必须修改] event 和 auto 模式被公开但运行时始终执行轮询 —
internal/corecmd/corecmd.go:608- 说明:WaitSpec 接受
event和auto,其中纯 event 模式甚至不要求status_query,但runDeclaredWaitPhase无条件调用wait.Run和WaitPoll,随后按StatusQuery提取状态;没有事件订阅、事件优先或回退逻辑。因此合法的 event 声明会因空查询立即失败,auto 也不会按契约优先使用事件。应在完整实现这些模式前只允许 poll,或为不同模式增加对应执行钩子和回退测试。
- 说明:WaitSpec 接受
-
[P1 | 必须修改] 等待超时可能返回轮询错误而不是 pending —
internal/wait/wait.go:127- 说明:超时检查只发生在一次成功轮询之后,而等待间隔使用不感知 context 的
time.Sleep。如果 deadline 在睡眠期间到达,循环仍会以已取消的 context 再调用一次 poller;正常尊重 context 的 poller会返回context deadline exceeded,最终被包装为失败,而不是契约规定的pending、timed_out=true。睡眠应使用 timer/select 监听ctx.Done(),并在发起每次轮询前处理 deadline。现有测试替换了 sleep,未覆盖真实的这一边界。
- 说明:超时检查只发生在一次成功轮询之后,而等待间隔使用不感知 context 的
-
[P1 | 必须修改] 非 ResultInvoke 命令观察到失败终态仍以成功退出 —
internal/corecmd/corecmd.go:638- 说明:框架允许 RunE、Orchestrate 和 Invoke 命令声明 Wait,但这些路径把 nil result 传入等待阶段。即使终态映射为 failure,函数也只向 stderr 打印摘要并返回 nil,因此进程退出码仍为 0,且原有输出无法反映最终失败;这与公开说明中失败终态应生成
error.type=wait、退出码 8 的契约不一致。应限制 Wait 只能搭配可重写统一信封的 ResultInvoke,或为旧路径返回能稳定映射到退出码 8 的类型化错误并明确输出契约,同时补充失败终态测试。
- 说明:框架允许 RunE、Orchestrate 和 Invoke 命令声明 Wait,但这些路径把 nil result 传入等待阶段。即使终态映射为 failure,函数也只向 stderr 打印摘要并返回 nil,因此进程退出码仍为 0,且原有输出无法反映最终失败;这与公开说明中失败终态应生成
此结论已作为 GitHub CHANGES_REQUESTED 提交;不会自动合并。
开源 DWS Codex 自动 CR:不通过,存在代码冲突feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次改动新增 Wait 契约、Schema 投影、轮询引擎以及统一结果的终态转换,整体分层清晰,但当前实现存在三项阻塞性语义缺陷:event/auto 模式没有对应执行路径,超时可能被误报为轮询失败,非统一结果命令的失败终态仍以成功退出。由于这些行为与公开的 Wait 契约和变更说明直接冲突,当前版本不应合并。未执行测试或仓库代码,结论仅基于静态审查。 CI 单测结果
结论说明Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。 代码问题
|
Pull request was converted to draft
# Conflicts: # internal/corecmd/corecmd.go
…tInvoke pairing Addresses the three P1 findings from review 4942891040: 1. event/auto modes were declared but always executed polls. WaitSpec now accepts poll only (event/auto fail validation with a not-implemented message); event_key/match_field dead fields removed. Event waiting will land with its own execution path and mode constant. 2. a deadline reached during the between-poll sleep re-polled with a cancelled context, so a context-aware poller surfaced its error as a poll failure instead of the contracted timed-out pending. The wait between polls now uses a timer + select on ctx.Done(), the deadline is checked before each poll, and a poll error on a cancelled context closes as timed-out pending with the last observed status. 3. legacy Invoke/Orchestrate/RunE commands declaring Wait observed a failure terminal while still exiting 0. validateWaitDecl now requires the ResultInvoke dispatcher (the only path whose unified envelope can be closed); the wait phase no longer wraps legacy paths. Also: dropped the unreachable nonPendingTerminal branch, simplified waitTimeoutSecs to the flag value (registration always seeds the reviewed default), and raised changed-code coverage to 100% (new wait-engine edge tests, output With* unit tests, contractfinal deep-copy coverage, AttachContract invalid-Wait panic path).
|
感谢 review,三个 P1 已在 1. event/auto 模式公开但运行时始终轮询 —— 采纳"实现前只允许 poll": 2. 超时可能被误报为轮询失败 —— 等待间隔改为 3. 非 ResultInvoke 命令失败终态仍 exit 0 —— 采纳"限制搭配": 另按 CR 精神清理了两处死代码(不可达的 |
Completes the wait capability per review guidance ("add corresponding
execution hooks and fallback tests for the modes"):
- contract.WaitSpec restores event/auto modes with event_key,
match_field, and a new resource_query (dotted path into the accepted
result data yielding the identifier events correlate against);
per-mode validation of required fields
- internal/wait adds EventStream (leaf-owned transport) and RunEvent:
correlated-event filtering, the same terminal/pending/unknown mapping
as polling, timed-out pending on deadline during consumption, and an
ErrEventStreamEnded sentinel distinguishing stream termination from
fail-closed status errors
- Spec.WaitEvents hook; validateWaitDecl pairs mode with hooks
(poll<->WaitPoll, event<->WaitEvents, auto<->both; surplus hooks
rejected too)
- the wait phase runs event-first in auto mode and falls back to polling
when the stream ends or the subscription fails, under one deadline
spanning both phases; strict event mode surfaces stream errors
- output.CommandResult gains Data() (deep copy) so the framework can
resolve the resource identifier without exposing mutable state
Changed-code coverage re-verified at 100% (CI cross-package recipe).
|
event / auto 模式已按评审建议补齐实现(
测试覆盖三种模式的 e2e(事件终态改写信封、auto 流终止回退轮询、auto 订阅失败回退、严格模式流终止报错、资源不可解析拒绝、跨资源事件忽略),改动行覆盖率仍为 100%(CI 同款跨包口径)。全量 |
wxianfeng
left a comment
There was a problem hiding this comment.
开源 DWS Codex 自动 CR:不通过,需修复
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
- 评审 SHA:
f118a369b004ac0dedc0cc4fe1ce2dabf4cd776b - CI 状态:未通过(29 通过,4 跳过,2 未通过)
- 冲突状态:无冲突
- Agent 测试报告:不涉及
- 指令 CI 集成测试:不涉及
- 自动 CR 结论:不通过,需修复
- 代码评审结论:不通过,需修复
变更总结
本次变更新增了声明式等待能力、轮询/事件等待引擎、Schema 投影以及统一结果的终态转换,整体分层和声明校验较完整。但执行路径存在两个阻塞问题:等待阶段会处理非 pending 的初始结果并可能覆盖业务失败;同时等待超时上下文没有传入叶子侧轮询和订阅建立操作,导致 --wait-timeout 无法保证按时返回。受安全边界限制,未执行测试或构建。
CI 单测结果
- ✅ AI Behavior — 通过
- ✅ CLI Smoke — 通过
- ✅ Coverage — 通过
- ✅ Coverage (baseline) — 通过
- ✅ Coverage (current) — 跳过
- ✅ Coverage (current: app) — 通过
- ✅ Coverage (current: cli) — 通过
- ✅ Coverage (current: generators) — 通过
- ✅ Coverage (current: helpers) — 通过
- ✅ Coverage (current: remaining) — 通过
- ✅ Coverage (macOS) — 跳过
- ✅ Coverage (supporting) — 通过
- ✅ Coverage (Windows) — 跳过
- ✅ Edition — 通过
- ✅ Interface Integrity — 通过
- ✅ Lint — 通过
- ✅ Mock MCP — 通过
- ✅ Policy — 通过
- ✅ route — 通过
- ❌ Test — 失败
- ✅ Test (cross-platform compile) — 通过
- ✅ Test (focused: ${{ matrix.shard }}) — 跳过
- ✅ Test (macOS auth/keychain) — 通过
- ✅ Test (race: app-a-b) — 通过
- ✅ Test (race: app-c) — 通过
- ✅ Test (race: app-d-r) — 通过
- ✅ Test (race: app-s-z-example-fuzz) — 通过
- ✅ Test (race: app-schema) — 通过
- ✅ Test (race: cli) — 通过
- ✅ Test (race: generators) — 通过
- ✅ Test (race: helpers) — 通过
- ❌ Test (race: remaining) — 失败
- ✅ Test (race: smoke) — 通过
- ✅ Test (Windows) — 通过
- ✅ Test (workflow and release contracts) — 通过
结论说明
Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。
代码问题
-
[P1 | 必须修改] 仅对 pending 结果启动等待阶段 —
internal/corecmd/corecmd.go:483- 说明:这里在
ResultInvoke返回任意CommandResult后都会执行等待阶段,没有检查初始 outcome。若业务调用已经返回output.Failure,--wait仍会继续轮询,并可能通过WithOutcome(..., success)覆盖原始失败;由于原错误信息仍保留,还会形成 success 携带 error 的非法信封,最终把真实业务错误变成框架校验错误。同步 success 也会被无意义地再次等待。应在进入等待循环前要求 outcome 为pending,对 success/failure/partial 原样返回,并补充初始失败不会调用WaitPoll/WaitEvents的测试。
- 说明:这里在
-
[P1 | 必须修改] 将等待截止上下文传入叶子侧 I/O —
internal/corecmd/corecmd.go:690- 说明:
runWaitLoop创建了带--wait-timeout截止时间的loopCtx,但轮询回调丢弃wait.Run传入的 context,只调用spec.WaitPoll(ctx);该Ctx内的 Cobra command 仍持有父 context。WaitEvents(ctx)建立订阅时也使用同一个父 context。因此,只要一次轮询 RPC 或订阅建立阻塞,声明的超时就无法取消它,命令可能在超时后无限挂住;auto 回退中的pollWithSpec同样如此。应让等待钩子显式接收context.Context(或保证其Ctx.Command().Context()为loopCtx),并覆盖阻塞轮询及阻塞订阅建立在超时后返回 pending 的测试。
- 说明:
此结论已作为 GitHub CHANGES_REQUESTED 提交;不会自动合并。
开源 DWS Codex 自动 CR:不通过,需修复feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次变更新增了声明式等待能力、轮询/事件等待引擎、Schema 投影以及统一结果的终态转换,整体分层和声明校验较完整。但执行路径存在两个阻塞问题:等待阶段会处理非 pending 的初始结果并可能覆盖业务失败;同时等待超时上下文没有传入叶子侧轮询和订阅建立操作,导致 CI 单测结果
结论说明Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。 代码问题
|
Only a pending ResultInvoke envelope enters the wait phase, so success, failure, and partial results are returned unchanged. WaitPoll/WaitEvents now receive the --wait-timeout deadline (and Command().Context() is bound to the same loop context) so a blocked poll or subscribe cannot hang past the declared timeout. Also allowlist the leftover multi drive_tree_list.py orphan that broke CI after merging main.
wxianfeng
left a comment
There was a problem hiding this comment.
开源 DWS Codex 自动 CR:不通过,需修复
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
- 评审 SHA:
c68603fea084bde86ae7127b3b90e4b107c55ac8 - CI 状态:通过(31 通过,4 跳过)
- 冲突状态:无冲突
- Agent 测试报告:不涉及
- 指令 CI 集成测试:不涉及
- 自动 CR 结论:不通过,需修复
- 代码评审结论:不通过,需修复
变更总结
本次变更新增了声明式等待契约、轮询/事件等待引擎、--wait/--wait-timeout 运行阶段,并将等待能力贯通到统一结果信封和 Schema 的完整/紧凑投影;声明与运行钩子的配对、未知状态失败关闭、超时保持 pending、事件模式回退轮询等主要路径设计一致。但超时秒数转换缺少上界校验,极大正整数会溢出为非正 time.Duration,从而意外取消截止时间并导致等待无限持续,这是合并前需要修复的可靠性问题。受审查限制,未执行测试或构建。
CI 单测结果
- ✅ AI Behavior — 通过
- ✅ CLI Smoke — 通过
- ✅ Coverage — 通过
- ✅ Coverage (baseline) — 通过
- ✅ Coverage (current) — 跳过
- ✅ Coverage (current: app) — 通过
- ✅ Coverage (current: cli) — 通过
- ✅ Coverage (current: generators) — 通过
- ✅ Coverage (current: helpers) — 通过
- ✅ Coverage (current: remaining) — 通过
- ✅ Coverage (macOS) — 跳过
- ✅ Coverage (supporting) — 通过
- ✅ Coverage (Windows) — 跳过
- ✅ Edition — 通过
- ✅ Interface Integrity — 通过
- ✅ Lint — 通过
- ✅ Mock MCP — 通过
- ✅ Policy — 通过
- ✅ route — 通过
- ✅ Test — 通过
- ✅ Test (cross-platform compile) — 通过
- ✅ Test (focused: ${{ matrix.shard }}) — 跳过
- ✅ Test (macOS auth/keychain) — 通过
- ✅ Test (race: app-a-b) — 通过
- ✅ Test (race: app-c) — 通过
- ✅ Test (race: app-d-r) — 通过
- ✅ Test (race: app-s-z-example-fuzz) — 通过
- ✅ Test (race: app-schema) — 通过
- ✅ Test (race: cli) — 通过
- ✅ Test (race: generators) — 通过
- ✅ Test (race: helpers) — 通过
- ✅ Test (race: remaining) — 通过
- ✅ Test (race: smoke) — 通过
- ✅ Test (Windows) — 通过
- ✅ Test (workflow and release contracts) — 通过
结论说明
Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。
代码问题
-
[P1 | 必须修改] 防止超大等待秒数溢出后禁用超时 —
internal/corecmd/corecmd.go:653- 说明:
--wait-timeout由int直接乘以time.Second。在 64 位平台传入例如9223372036854775807时会溢出成负的time.Duration;随后runWaitLoop和wait.Run都只在 timeout > 0 时创建截止时间,因此这个本应有限的等待会变成无期限轮询或事件阻塞。该值能够由 pflag 正常解析,用户输入即可触发。请在转换前限制秒数不超过math.MaxInt64/int64(time.Second)(并对越界返回 validation 错误),或使用带溢出检查的转换;同时增加极大正值的回归测试。
- 说明:
此结论已作为 GitHub CHANGES_REQUESTED 提交;不会自动合并。
开源 DWS Codex 自动 CR:不通过,需修复feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次变更新增了声明式等待契约、轮询/事件等待引擎、 CI 单测结果
结论说明Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。 代码问题
|
int(secs)*time.Second can wrap a pflag-legal MaxInt64 into a non-positive duration, which skipped the wait deadline and waited forever. Convert with an overflow check and return a validation error instead.
wxianfeng
left a comment
There was a problem hiding this comment.
开源 DWS Codex 自动 CR:不通过,需修复
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
- 评审 SHA:
e625da4c27508b2a70e6c35dfc5667adbac934fb - CI 状态:未通过(30 通过,4 跳过,1 未通过)
- 冲突状态:无冲突
- Agent 测试报告:不涉及
- 指令 CI 集成测试:不涉及
- 自动 CR 结论:不通过,需修复
- 代码评审结论:不通过,需修复
变更总结
本次变更新增了声明式等待能力、轮询/事件执行引擎、Schema 投影以及统一结果的等待终态映射;模式与 hook 配对、超时共享及未知状态失败关闭的整体设计较完整。但终态封装没有同步已观察到的 operation 状态,会产生自相矛盾的机器输出,因此当前实现不应合并。受安全限制未执行测试或构建,仅完成静态检查。
CI 单测结果
- ✅ AI Behavior — 通过
- ✅ CLI Smoke — 通过
- ❌ Coverage — 失败
- ✅ Coverage (baseline) — 通过
- ✅ Coverage (current) — 跳过
- ✅ Coverage (current: app) — 通过
- ✅ Coverage (current: cli) — 通过
- ✅ Coverage (current: generators) — 通过
- ✅ Coverage (current: helpers) — 通过
- ✅ Coverage (current: remaining) — 通过
- ✅ Coverage (macOS) — 跳过
- ✅ Coverage (supporting) — 通过
- ✅ Coverage (Windows) — 跳过
- ✅ Edition — 通过
- ✅ Interface Integrity — 通过
- ✅ Lint — 通过
- ✅ Mock MCP — 通过
- ✅ Policy — 通过
- ✅ route — 通过
- ✅ Test — 通过
- ✅ Test (cross-platform compile) — 通过
- ✅ Test (focused: ${{ matrix.shard }}) — 跳过
- ✅ Test (macOS auth/keychain) — 通过
- ✅ Test (race: app-a-b) — 通过
- ✅ Test (race: app-c) — 通过
- ✅ Test (race: app-d-r) — 通过
- ✅ Test (race: app-s-z-example-fuzz) — 通过
- ✅ Test (race: app-schema) — 通过
- ✅ Test (race: cli) — 通过
- ✅ Test (race: generators) — 通过
- ✅ Test (race: helpers) — 通过
- ✅ Test (race: remaining) — 通过
- ✅ Test (race: smoke) — 通过
- ✅ Test (Windows) — 通过
- ✅ Test (workflow and release contracts) — 通过
结论说明
Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。
代码问题
-
[P1 | 必须修改] 等待结束后仍发布受理阶段的 operation 状态 —
internal/corecmd/corecmd.go:653- 说明:轮询或事件已经观察到终态后,这里只修改顶层 outcome;只有超时分支会通过 WithOperationTimedOut 更新 meta.operation.state。由于 Pending 结果必须携带异步 operation,成功或失败终态因此会继续保留初始的 processing 等状态,例如同时输出 outcome=success 与 meta.operation.state=processing,失败时也可能保留同样的旧状态。机器消费者无法据此判断真实终态,也违背了等待功能宣称的“关闭到终态”。应在 success/failure 两个分支都用 outcome.Status 更新 operation.state(并明确终态是否还应保留 operation 元数据),并增加最终发射信封的断言。
-
[P2 | 建议修改] WaitSpec 校验使用裁剪值但运行时保留原始状态值 —
internal/corecmd/contract/types.go:144- 说明:Validate 对 PendingValues 使用 TrimSpace 后检查冲突,却没有把裁剪后的值写回;Terminal 的键也只检查裁剪后是否为空。随后运行引擎直接用原始 PendingValues 和 Terminal 建表,因此声明如 pending_values=[" processing "] 或 terminal 键带空格能够通过校验及发布 Schema,却会把后端返回的 "processing" 判为未知状态。应要求状态值已经规范化,或在构造最终 WaitSpec 时深拷贝并统一裁剪,同时检测裁剪后重复项和冲突。
此结论已作为 GitHub CHANGES_REQUESTED 提交;不会自动合并。
开源 DWS Codex 自动 CR:不通过,需修复feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次变更新增了声明式等待能力、轮询/事件执行引擎、Schema 投影以及统一结果的等待终态映射;模式与 hook 配对、超时共享及未知状态失败关闭的整体设计较完整。但终态封装没有同步已观察到的 operation 状态,会产生自相矛盾的机器输出,因此当前实现不应合并。受安全限制未执行测试或构建,仅完成静态检查。 CI 单测结果
结论说明Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。 代码问题
|
wxianfeng
left a comment
There was a problem hiding this comment.
开源 DWS Codex 自动 CR:不通过,需修复
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
- 评审 SHA:
33312531c6f971db30d769c90017bbf5363465aa - CI 状态:未通过(27 通过,4 跳过,4 未通过)
- 冲突状态:无冲突
- Agent 测试报告:不涉及
- 指令 CI 集成测试:不涉及
- 自动 CR 结论:不通过,需修复
- 代码评审结论:不通过,需修复
变更总结
本次变更新增了统一的 wait 契约、轮询/事件等待引擎、结果终态转换、Schema 投影及较完整的超时和失败测试;主路径 corecmd.New 能校验声明与 hook 配对并仅为支持的命令注册参数。但旁路声明 API AttachContract 仍可发布无法执行的 wait 能力,破坏“声明即运行时能力”的一致性,因此当前不应合并。受安全限制未执行测试。
CI 单测结果
- ✅ AI Behavior — 通过
- ✅ CLI Smoke — 通过
- ❌ Coverage — 失败
- ❌ Coverage (baseline) — 取消
- ✅ Coverage (current) — 跳过
- ❌ Coverage (current: app) — 取消
- ✅ Coverage (current: cli) — 通过
- ✅ Coverage (current: generators) — 通过
- ❌ Coverage (current: helpers) — 取消
- ✅ Coverage (current: remaining) — 通过
- ✅ Coverage (macOS) — 跳过
- ✅ Coverage (supporting) — 通过
- ✅ Coverage (Windows) — 跳过
- ✅ Edition — 通过
- ✅ Interface Integrity — 通过
- ✅ Lint — 通过
- ✅ Mock MCP — 通过
- ✅ Policy — 通过
- ✅ route — 通过
- ✅ Test — 通过
- ✅ Test (cross-platform compile) — 通过
- ✅ Test (focused: ${{ matrix.shard }}) — 跳过
- ✅ Test (macOS auth/keychain) — 通过
- ✅ Test (race: app-a-b) — 通过
- ✅ Test (race: app-c) — 通过
- ✅ Test (race: app-d-r) — 通过
- ✅ Test (race: app-s-z-example-fuzz) — 通过
- ✅ Test (race: app-schema) — 通过
- ✅ Test (race: cli) — 通过
- ✅ Test (race: generators) — 通过
- ✅ Test (race: helpers) — 通过
- ✅ Test (race: remaining) — 通过
- ✅ Test (race: smoke) — 通过
- ✅ Test (Windows) — 通过
- ✅ Test (workflow and release contracts) — 通过
结论说明
Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。
代码问题
-
[P1 | 必须修改] 禁止 AttachContract 发布没有运行时实现的 wait 能力 —
internal/corecmd/corecmd.go:1731- 说明:
AttachContract会无条件把decl.Wait写入 ContractFinal,随后 Catalog 将其作为正向能力发布;但这个路径既不注册--wait/--wait-timeout,也没有WaitPoll、WaitEvents或等待阶段可执行。任何通过 Tier2/现有 Cobra 迁移路径附加该契约的命令都会在 Schema 中声称支持 wait,而真实 CLI 对示例返回 unknown flag。应让 wait 只能经corecmd.New的完整声明/实现配对发布,或让 AttachContract 明确拒绝非空 Wait,并增加 Schema 到真实 CLI 参数的回归测试。
- 说明:
此结论已作为 GitHub CHANGES_REQUESTED 提交;不会自动合并。
开源 DWS Codex 自动 CR:不通过,需修复feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次变更新增了统一的 wait 契约、轮询/事件等待引擎、结果终态转换、Schema 投影及较完整的超时和失败测试;主路径 CI 单测结果
结论说明Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。 代码问题
|
Review 5343447650 (P2): AttachContract wrote any non-empty decl.Wait into ContractFinal, so Tier2 callers (DeclareLeafMetadata / direct attaches) could publish a wait capability with no runtime behind it — no WaitPoll/WaitEvents pairing, no --wait/--wait-timeout registration, no wait phase — leaving Schema advertising a capability the CLI rejects at flag parse, while validateWaitDecl only guards the corecmd.New path. The overlay path now panics on a wait declaration and points authors at the managed New construction. New keeps publishing wait through the new unexported attachContractPayload, which only runs after validateWaitDecl proved the hook pairing and registerWaitFlags bound the flags.
…embly Review 4969419359 (P1): WaitSpec.Validate only proved poll_command was non-empty, so a typo, a removed command, or a non-runnable path could still reach the final Schema while the runtime kept working through the private WaitPoll hook — the declared manual resume path drifted from the command tree. validateWaitPollCommands now runs in assembleSchemaRegistryFromBound, the one phase that sees the complete bound registry and the final tool set at once. Each poll/auto wait declaration must resolve (canonical or CLI path form) to a bound public command that is delivered in the same Schema and carries a read safety effect. Regression tests cover the missing, excluded, undelivered, and non-read targets plus the accepted forms, the event-mode exemption, index error propagation, and the full production assembly path both accepting a resolved target and rejecting an unresolvable one.
|
感谢 review,两处意见已在 1. [P2] 禁止 AttachContract 发布没有运行时实现的等待能力 —— 采纳"拒绝非空 Wait"方案: 2. CI 失败(Coverage/Test 8 个 job) —— 根因是 runner 侧 另:上一轮 4969419359 的 P1( |
wxianfeng
left a comment
There was a problem hiding this comment.
开源 DWS Codex 自动 CR:不通过,需修复
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
- 评审 SHA:
193f7555821df6381423e77e635ce216364a8eb7 - CI 状态:未通过(29 通过,4 跳过,2 未通过)
- 冲突状态:无冲突
- Agent 测试报告:不涉及
- 指令 CI 集成测试:不涉及
- 自动 CR 结论:不通过,需修复
- 代码评审结论:不通过,需修复
变更总结
本次变更新增声明驱动的 --wait/--wait-timeout 能力,包括轮询、事件订阅、auto 回退、Schema 投影及统一结果封装;声明归一化、轮询命令可见性校验和终态输出映射整体一致。但等待引擎把调用方取消与自身超时混为一谈,会破坏 Ctrl-C 等中断语义。受审查约束限制,未执行测试或构建。
CI 单测结果
- ✅ AI Behavior — 通过
- ✅ CLI Smoke — 通过
- ❌ Coverage — 失败
- ✅ Coverage (baseline) — 通过
- ✅ Coverage (current) — 跳过
- ✅ Coverage (current: app) — 通过
- ✅ Coverage (current: cli) — 通过
- ✅ Coverage (current: generators) — 通过
- ✅ Coverage (current: helpers) — 通过
- ✅ Coverage (current: remaining) — 通过
- ✅ Coverage (macOS) — 跳过
- ✅ Coverage (supporting) — 通过
- ✅ Coverage (Windows) — 跳过
- ✅ Edition — 通过
- ✅ Interface Integrity — 通过
- ✅ Lint — 通过
- ✅ Mock MCP — 通过
- ❌ Policy — 失败
- ✅ route — 通过
- ✅ Test — 通过
- ✅ Test (cross-platform compile) — 通过
- ✅ Test (focused: ${{ matrix.shard }}) — 跳过
- ✅ Test (macOS auth/keychain) — 通过
- ✅ Test (race: app-a-b) — 通过
- ✅ Test (race: app-c) — 通过
- ✅ Test (race: app-d-r) — 通过
- ✅ Test (race: app-s-z-example-fuzz) — 通过
- ✅ Test (race: app-schema) — 通过
- ✅ Test (race: cli) — 通过
- ✅ Test (race: generators) — 通过
- ✅ Test (race: helpers) — 通过
- ✅ Test (race: remaining) — 通过
- ✅ Test (race: smoke) — 通过
- ✅ Test (Windows) — 通过
- ✅ Test (workflow and release contracts) — 通过
结论说明
Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。
代码问题
-
[P1 | 必须修改] 不要把调用方取消伪装成等待超时成功 —
internal/wait/wait.go:94- 说明:
Run在任何ctx.Err()情况下都返回TimedOut:true且无错误,RunEvent也采用相同逻辑;而传入的 context 同时继承命令根 context,所以用户按 Ctrl-C、上层主动取消或父级截止时间到期时,也会被runDeclaredWaitPhase包装成outcome=pending、timed_out=true并以退出码 0 返回。这既谎报为--wait-timeout,也丢失仓库已有的 SIGINT=130/取消错误语义。应区分等待阶段自己创建的超时与父 context 的取消:只有内部 wait deadline 到期才能生成 pending timeout,父级取消必须向上传播;轮询、订阅以及 auto 回退路径都应补充回归测试。
- 说明:
此结论已作为 GitHub CHANGES_REQUESTED 提交;不会自动合并。
开源 DWS Codex 自动 CR:不通过,需修复feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次变更新增声明驱动的 CI 单测结果
结论说明Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。 代码问题
|
The coverage gate (99.45% changed code) flagged two leftovers from 8802dcf's waitResource refactor plus one branch my overlay split made unreachable: - waitResource's slow path marshal error now has a regression case: a DTO with a channel field drives json.Marshal to failure and surfaces 'cannot be serialized to JSON'. - attachContractPayload no longer re-normalizes Contract.Wait behind an error panic that could never fire: New's normalizeWaitDecl already canonicalized and validated the declaration in place, AttachContract rejects wait declarations outright, and the store deep-copies the terminal map. The branch was dead code, not a guard.
开源 DWS Codex 自动 CR:不通过,CI 未通过feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结该 PR 新增了声明式 wait 能力,贯通 Contract、Schema、轮询/事件引擎和统一输出,并对终态映射、事件关联、轮询命令可见性及只读属性做了较完整的闭合校验。但 CI 单测结果
结论说明自动 CR 不通过,原因如下:
代码问题
|
The Policy job's merge-revision sanity check compares the event's pull_request.base.sha with the first parent of refs/pull/N/merge. Upstream main advanced between event delivery and the merge-ref rebuild (d1f1ab7 vs 5aaf2ef), so the check failed on infrastructure timing with every other job green. A fresh synchronize event realigns both.
开源 DWS Codex 自动 CR:不通过,CI 未通过feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次变更新增声明式等待能力,包括 poll/event/auto 三种模式、统一结果终态转换、超时取消、Schema 投影及跨命令轮询目标校验;运行时声明与 hook 配对、未知状态失败关闭、失败终态专用退出码和主要超时路径均有针对性测试。发现一处非阻塞的 Schema 可维护性缺口:新增 wait 字段未纳入字段级 provenance 不变量。受审查限制未执行测试或构建,仅完成静态检查。 CI 单测结果
结论说明自动 CR 不通过,原因如下:
代码问题
|
|
跟进: 本轮补齐了 coverage 门禁上一处真实缺口(changed code 99.45% → 100%): 另说明两次非代码 CI 失败的处理:上一轮 Coverage/Test 取消是 runner apt 源(azure.archive.ubuntu.com)不可达导致 |
wxianfeng
left a comment
There was a problem hiding this comment.
开源 DWS Codex 自动 CR:不通过,需修复
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
- 评审 SHA:
513337fcf9ae9d8b55bb3aca29a387eac2236d77 - CI 状态:通过(31 通过,4 跳过)
- 冲突状态:无冲突
- Agent 测试报告:不涉及
- 指令 CI 集成测试:不涉及
- 自动 CR 结论:不通过,需修复
- 代码评审结论:不通过,需修复
变更总结
本次变更新增了声明式 wait 能力,包括 poll/event/auto 等待模式、Schema 投影、统一结果终态转换及专用退出码;声明、运行时与 Catalog 的大部分约束保持一致,且覆盖了超时、未知状态、事件回退和终态封装。静态审阅发现事件订阅存在一个未封闭的 nil 返回边界,可导致用户命令直接 panic;未执行测试或构建。
CI 单测结果
- ✅ AI Behavior — 通过
- ✅ CLI Smoke — 通过
- ✅ Coverage — 通过
- ✅ Coverage (baseline) — 通过
- ✅ Coverage (current) — 跳过
- ✅ Coverage (current: app) — 通过
- ✅ Coverage (current: cli) — 通过
- ✅ Coverage (current: generators) — 通过
- ✅ Coverage (current: helpers) — 通过
- ✅ Coverage (current: remaining) — 通过
- ✅ Coverage (macOS) — 跳过
- ✅ Coverage (supporting) — 通过
- ✅ Coverage (Windows) — 跳过
- ✅ Edition — 通过
- ✅ Interface Integrity — 通过
- ✅ Lint — 通过
- ✅ Mock MCP — 通过
- ✅ Policy — 通过
- ✅ route — 通过
- ✅ Test — 通过
- ✅ Test (cross-platform compile) — 通过
- ✅ Test (focused: ${{ matrix.shard }}) — 跳过
- ✅ Test (macOS auth/keychain) — 通过
- ✅ Test (race: app-a-b) — 通过
- ✅ Test (race: app-c) — 通过
- ✅ Test (race: app-d-r) — 通过
- ✅ Test (race: app-s-z-example-fuzz) — 通过
- ✅ Test (race: app-schema) — 通过
- ✅ Test (race: cli) — 通过
- ✅ Test (race: generators) — 通过
- ✅ Test (race: helpers) — 通过
- ✅ Test (race: remaining) — 通过
- ✅ Test (race: smoke) — 通过
- ✅ Test (Windows) — 通过
- ✅ Test (workflow and release contracts) — 通过
结论说明
Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。
代码问题
-
[P1 | 必须修改] 事件订阅返回 nil stream 会触发运行时 panic —
internal/corecmd/corecmd.go:741- 说明:
WaitEvents的返回值只检查了err,没有验证stream非空;随后无条件传给wait.RunEvent,后者会调用stream.Recv。只要叶子实现或订阅适配器在无错误时意外返回(nil, nil),执行--wait就会因 nil 接口调用而崩溃,而不是通过统一错误信封失败;auto 模式也无法按设计回退到 polling。请在调用RunEvent前拒绝 nil stream(严格 event 模式返回明确错误,auto 模式可按订阅失败规则回退),并补充 event/auto 两种模式的回归测试。
- 说明:
此结论已作为 GitHub CHANGES_REQUESTED 提交;不会自动合并。
开源 DWS Codex 自动 CR:不通过,需修复feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次变更新增了声明式 wait 能力,包括 poll/event/auto 等待模式、Schema 投影、统一结果终态转换及专用退出码;声明、运行时与 Catalog 的大部分约束保持一致,且覆盖了超时、未知状态、事件回退和终态封装。静态审阅发现事件订阅存在一个未封闭的 nil 返回边界,可导致用户命令直接 panic;未执行测试或构建。 CI 单测结果
结论说明Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。 代码问题
|
WaitEvents' return was only checked for err, so a leaf or subscription adapter returning (nil, nil) reached wait.RunEvent, whose first Recv panicked on the nil interface — a user-facing crash instead of the unified error envelope, and auto mode could not fall back to polling as designed. runWaitLoop now rejects a nil stream before the event loop: strict event mode fails through the envelope with 'event subscription failed: subscription returned no stream'; auto mode falls back to polling under the subscription-failure rule (a deadline that already expired during subscribe still closes as timed-out pending with no fallback, preserving the shared-deadline contract). Regression tests cover both modes.
|
本轮 P1 已在 |
wxianfeng
left a comment
There was a problem hiding this comment.
开源 DWS Codex 自动 CR:不通过,需修复
feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
- 评审 SHA:
5f1f13cd8959d6398ecd2fea158389a6dce4caa5 - CI 状态:未通过(30 通过,4 跳过,1 未通过)
- 冲突状态:无冲突
- Agent 测试报告:不涉及
- 指令 CI 集成测试:不涉及
- 自动 CR 结论:不通过,需修复
- 代码评审结论:不通过,需修复
变更总结
本次 PR 增加了声明式等待能力,贯通了 Contract、Schema、poll/event/auto 运行期和统一结果信封,并对 poll_command 的可见性与只读属性进行了校验。整体结构一致,但 auto 模式从事件流回退轮询后发生超时时,会丢失事件阶段已观察到的最新状态,违反超时结果必须报告真实最后状态的契约,建议修复后合并。审查仅基于静态 Git diff,未执行测试或构建。
CI 单测结果
- ✅ AI Behavior — 通过
- ✅ CLI Smoke — 通过
- ✅ Coverage — 通过
- ✅ Coverage (baseline) — 通过
- ✅ Coverage (current) — 跳过
- ✅ Coverage (current: app) — 通过
- ✅ Coverage (current: cli) — 通过
- ✅ Coverage (current: generators) — 通过
- ✅ Coverage (current: helpers) — 通过
- ✅ Coverage (current: remaining) — 通过
- ✅ Coverage (macOS) — 跳过
- ✅ Coverage (supporting) — 通过
- ✅ Coverage (Windows) — 跳过
- ✅ Edition — 通过
- ✅ Interface Integrity — 通过
- ✅ Lint — 通过
- ✅ Mock MCP — 通过
- ❌ Policy — 失败
- ✅ route — 通过
- ✅ Test — 通过
- ✅ Test (cross-platform compile) — 通过
- ✅ Test (focused: ${{ matrix.shard }}) — 跳过
- ✅ Test (macOS auth/keychain) — 通过
- ✅ Test (race: app-a-b) — 通过
- ✅ Test (race: app-c) — 通过
- ✅ Test (race: app-d-r) — 通过
- ✅ Test (race: app-s-z-example-fuzz) — 通过
- ✅ Test (race: app-schema) — 通过
- ✅ Test (race: cli) — 通过
- ✅ Test (race: generators) — 通过
- ✅ Test (race: helpers) — 通过
- ✅ Test (race: remaining) — 通过
- ✅ Test (race: smoke) — 通过
- ✅ Test (Windows) — 通过
- ✅ Test (workflow and release contracts) — 通过
结论说明
Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。
代码问题
-
[P1 | 必须修改] auto 回退超时会丢失事件阶段的最后状态 —
internal/corecmd/corecmd.go:722- 说明:
RunEvent返回流提前结束错误时,其Outcome已包含最后一个相关事件的状态;这里直接调用pollWithSpec丢弃了该值。若回退后的首次轮询阻塞直至共享 deadline,wait.Run会以空Status返回超时,随后WithOperationTimedOut("")保留最初受理状态(例如NEW),而不是事件阶段已经观察到的RUNNING。这会让meta.operation.state和超时提示回退到陈旧状态,违反本 PR 所声明的“超时保持最后观察到的真实状态”契约。应在 auto 两阶段之间传递最后状态,并在轮询尚未产生新状态就超时时使用它,同时补充“事件观察到 pending → 流结束 → 回退轮询阻塞超时”的回归测试。
- 说明:
此结论已作为 GitHub CHANGES_REQUESTED 提交;不会自动合并。
开源 DWS Codex 自动 CR:不通过,需修复feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次 PR 增加了声明式等待能力,贯通了 Contract、Schema、poll/event/auto 运行期和统一结果信封,并对 poll_command 的可见性与只读属性进行了校验。整体结构一致,但 auto 模式从事件流回退轮询后发生超时时,会丢失事件阶段已观察到的最新状态,违反超时结果必须报告真实最后状态的契约,建议修复后合并。审查仅基于静态 Git diff,未执行测试或构建。 CI 单测结果
结论说明Codex 静态审查发现 P0/P1 阻断问题,需要修复后提交新 commit 重新 CR。 代码问题
|
…k timeout RunEvent dropped lastStatus from its stream-ended outcome, and the auto fallback called pollWithSpec without it, so a fallback poll that blocked until the shared deadline timed out with an empty status: the envelope kept the accepted result's original state (e.g. NEW) instead of the state the events had already observed (e.g. RUNNING), violating the declared 'timeout reports the real last observed state' contract. RunEvent's ErrEventStreamEnded outcome now carries the last correlated status, and pollWithSpec adopts it when the poll loop ends without observing a status of its own (first poll blocked to the deadline). The subscribe-failure fallback keeps passing empty (nothing was observed). Regression test: event observes pending RUNNING → stream ends → fallback poll blocks → timeout envelope reports state RUNNING with timed_out=true, never regressing to NEW.
|
本轮 P1 已在 问题确实如 review 所述,且比描述多一层:
回归测试 关于本轮 CI 的 Policy 失败: |
开源 DWS Codex 自动 CR:不通过,CI 未通过feat(corecmd): declarative Wait capability (Contract.Wait + framework wait phase)
变更总结本次变更新增了声明式等待契约、轮询/事件等待引擎、统一结果终态转换、Schema 投影及较完整的框架测试,整体分层和超时失败关闭策略较一致。但等待能力与统一输出 rollout 的构造期约束缺失,可能发布实际无法执行的 CI 单测结果
结论说明自动 CR 不通过,原因如下:
代码问题
|
Upstream main carried fully-consumed flag migrations in approved-flag-migrations-v1.json whose references (stable v1.0.59, merge-base, and current tree) all already reached the after state, so the interface migration lifecycle demanded their removal — main's own interface-integrity gate fails the same way. Dropping them is exactly what the policy check prescribes; the 4 still-active migrations stay.
What
Framework-only PR: adds the reviewed terminal-state wait capability (
Contract.Wait), mirroring the established DryRunSpec pattern end to end. Updated after review 4942891040: poll-only mode, ResultInvoke-only pairing, deadline-safe wait loop. No business command declares it yet — approval/export/batch adoption lands separately.Solves the agent pattern where every async command (approval flows, exports, batch jobs) forces the agent to hand-roll a poll loop: expensive in tokens, blind on exit semantics, and inconsistent across products.
Design (one paragraph)
A command declares
contract.WaitSpec(modepoll— the only implemented mode today, event/auto fail validation until they ship with their own execution path;poll_command,status_query, explicitterminalstatus→success/failure map,pending_values, revieweddefault_timeout_secs). Declaration, the runtimeWaitPollhook, and theResultInvokedispatcher are paired at construction (any mismatch panics — same philosophy asvalidateDispatchDecl; legacy Invoke/Orchestrate/RunE paths cannot declare wait because their emitted output cannot be closed into the terminal envelope). Declared leaves register--wait/--wait-timeoutnatively on Cobra (neverFlagSpec, so they can never leak into MCP toolArgs); undeclared leaves reject the flags as unknown-flag instead of silently ignoring them. After a successful dispatch, the framework wait phase polls through the leaf hook: immediate first poll, ×1.5 backoff capped at 30s, fail-closed on unknown status (a status neither terminal nor pending is an error, not a "not yet").Envelope / exit-code semantics
The wait phase runs after a successful ResultInvoke dispatch and closes the accepted unified envelope exactly once. Deadline exhaustion anywhere (before a poll, during a context-aware poll, or between polls) closes as timed-out pending, never as a poll failure:
outcome: successoutcome: failure+ new wire-stableerror.type: "wait",subtype: "terminal_failure"partial_failure=7)outcome: pending,meta.operation.timed_out: true+ last observedstate, preserving declaredid/next_commandresume factsWire-format additions are additive only: new optional catalog key
wait(omitempty — no declaring command ⇒ byte-identical wire, verified below), newerror.type "wait"+ exit code 8 documented ininternal/errorscode-table comment and RFC §5.0.Files
internal/corecmd/contract/types.go—WaitSpec+ closed-setValidateinternal/corecmd/contract_decl.go/contract/final.go/contractfinal/store.go— declaration → Final payload (deep-copies terminal map)internal/corecmd/corecmd.go—Spec.WaitPoll, construction pairing, flag registration, wait phase across all managed dispatch paths,Ctx.Wait()internal/wait/— engine: poll cadence, dotted status extraction, outcome mapping (DTO-only dep oncontract)internal/cli/*—waitprojected through ToolSpec / snapshot wire / compact+optional whitelistsinternal/output/*—WithOutcome/WithErrorInfo/WithOperationTimedOut; invariants I2/I3 and pending-requires-operation preserved.changes/wait-framework-capability.md— release fragmentVerification
DWS_PACKAGE_VERSION=0.0.0-test go test ./...— green-coverpkgcross-package recipe: ALL CHANGED LINES COVERED)./scripts/policy/check-generated-drift.sh— ok (assembly deterministic, registry/source hashes stable)./scripts/policy/check-schema-catalog.sh— ok (27 products, 1121 tools, wire unchanged)