Repository navigation
feat(core): bind @-workflow confirms to a real WorkflowRun of the chosen snapshot - #114
Conversation
Pending WorkflowRun details must retain JSON null for startedAt and finishedAt instead of invalid empty date-time strings. TestPendingWorkflowRunDetailPreservesNullTimestamps directly checks both fields over HTTP. Build and full core/integration validation are logged per commit; the known Windows Git integration failure remains unchanged.
Create and link WorkflowRun/IssueRun atomically for Confirm and manual starts, bypass legacy dispatch, and enforce tenant-scoped one-to-one references in migration 0036. Settle all unlinked non-terminal history, retain editable terminal rows, and validate constraints on every database. Preserve the admission base live-form divergence gate verbatim. HTTP, concurrency, snapshot freeze and fresh/seeded migration regressions accompany this change; full core/integration and build checks are logged at this commit.
Project authoritative state in the caller transaction, write system activities and summaries, and preserve rollback/idempotency. Supplement run.started before direct terminal outcomes only with actual start evidence; awaiting_input leaves the IssueRun unchanged without rejecting the source transaction. PostgreSQL regressions cover these paths. Scheduler wiring remains deferred; full core/integration and build checks are logged at this commit.
Keep one interaction through confirm retries, show authoritative WorkflowRun status and link to its detail page. Historical rows without an invocation reference retain IssueRun status and have no run link. When the user selects a published version, load its snapshot form with issue context, render that version's fields, discard obsolete answers and retain the selected snapshot instead of the descriptor's newest-version default. Component/API regressions and bilingual module docs accompany the feature. The existing backend field-divergence gate stays unchanged; no polling or backend endpoint is added. The final rebased tree is validated with Go build, full core/integration packages and focused frontend gates. Race, per-commit rebuilds and HTTP/browser rechecks are explicitly excluded from this rebase follow-up.
Move invocation linkage to migration 0037, reserving 0035 and 0036 for the earlier identity and execution work. Explain why the composite foreign key fails closed on unexpected dangling references. Identify the platform snapshot selector using all published snapshot ids from the existing paginated endpoint, preserving an author's same-named select as ordinary input when its options are custom. Explain how to resolve workflow_version_fields_changed in the composer without changing admission policy. Validation: Go build, the core package, all migration and invocation integration cases with a 30-minute timeout, and touched-file frontend format, lint, types, component/API and module gates. No scheduler or identity changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two field-divergence tests repeated the same 409 handler, render and confirm steps, which the jscpd duplication gate rejects. Docs-Unchanged: test-only refactor; no behavior or module contract changed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep both README sections: main's session dashboard / model request statistics and this branch's workflow invocation linkage; list the 0035 model-attempt migration before 0037 in the migrations README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reuse the real .orax, HTTPS marketplace and PostgreSQL harness to prove imported workflows enter the Issue target directory and their selected snapshot forms remain stable across administrators and import-trigger deactivation. Compare pending run bindings across those identities. Verify Confirm links its IssueRun to a WorkflowRun bound to the imported snapshot, direct starts select the same snapshot, and editing the draft cannot replace the frozen graph. After provider uninstall, preserve the old snapshot, publish a new version and create a run from it. Evidence stops at WorkflowRun creation and binding; real node execution remains with ora-space#70. No import implementation or specs changes. Validation: go build ./..., the core package, three new import invocation cases and 23 existing invocation/confirm/assets regressions with 30-minute timeouts. The merge has no overlap with this branch's frontend edits, so the conditional frontend gate does not apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… linkage Keep this branch's WorkflowRun linkage behaviour while adopting main's "Issue collaboration D9" citations in place of the removed migration notes, and list main's 0036 asset-import migration before 0037. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inkage main rebuilt docs/ and removed the accreted docs tree, so the invocation linkage notes move into docs/collaboration/interactions and docs/workflows/workflows. main also gained 0037_repository_bindings, and the persistence guide now requires a new migration to follow the latest number on main, so the invocation reference migration becomes 0038. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained 0038_agent_workspace_blocked_deadline, so the invocation reference migration moves to 0039 to keep following the latest number on main; the migration READMEs list main's 0038 before it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue 协作文档仍把确认前的表单描述为始终取自当前文档,与前端选择已发布版本后重取快照表单的行为不符。 同步修正中英文说明,区分初次打开与选择快照后的表单来源,明确现有活稿字段差异 409 检查仍然生效,并在流程图显示关联创建的两类运行。 验证:对照 WorkflowInteractionComposer、useWorkflowSnapshotForm 和 confirmInteraction 的现有实现;两份文档的相对文件链接均有效;git diff --check 通过。 仅修改文档,不改变 API、字段差异门禁、迁移或调度行为。
合并 c6cccaf,适配已合入的运行身份、执行器槽位与真实会话执行。 direct、Confirm 与 manual 共用创建函数;后两者冻结实际调用者、Issue Project、插件版本和执行器绑定,缺 Project 时事务完整回滚,不使用开发模拟器伪造执行。 将尚未合入的调用引用迁移顺延到 0043,保留 main 的 0039–0042,并同步中英迁移与运行说明。 验证:真实 PostgreSQL 聚焦 core/integration 测试通过,无跳过;身份冻结、缺 Project 回滚和并发 Confirm 定向 race 通过;checkformat、task lint、diff --check 通过。 本次不包含调度器向 IssueRun 的状态投影调用和真实 Node 联合验收,PR 仍按 preflight 范围交付。
保留既有提交历史,合并 upstream/main 的 ceb39bc 外部 Git 线路,解决中英文迁移 README 冲突。 保留已合入的 0043_external_git_line,将尚未合入的 WorkflowRun 调用引用迁移顺延为 0044,并同步升级测试常量与中英说明。 新主线没有改变 direct、Confirm、manual 的共用创建函数及 created_by/project_id/plugin_version 冻结语义;本次不改运行创建实现。 定向测试发现新主线路由漏传 SlotName,使具体槽位 PUT 被当成列表请求返回 405;恢复该字段,由已有身份冻结测试直接覆盖。 验证:真实 PostgreSQL 定向测试中9个顶层用例初次通过;恢复SlotName后仅重跑剩余身份冻结用例,confirm/manual均通过,0跳过。合并代码checkformat/lint通过,字段修复后路由文件格式及router包lint通过。已通过的其余用例未重复运行。 调度器向 IssueRun 投影调用、fixtures=false 真实 Node 联合验收仍未交付,本 PR 保持 preflight。
…nto invocation linkage main gained 0044_model_attempt_workflow_run, so the invocation reference migration moves to 0045; the migration READMEs list main's 0044 before it. cloud#94 fixed the missing SlotName on main, so router.go now matches main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Project only successful workflow status writes in the same transaction. Propagate settle CAS outcomes before session dispatch or cleanup, and truncate existing output/error summaries at UTF-8 boundaries within 20000 bytes. Add PostgreSQL coverage for Confirm lifecycle, repeat invocation after completion, deterministic failure, concurrent passes, lost CAS, summary limits, and atomic rollback. Validate with task format, go run ./cmd/checkformat, task lint, targeted DB tests, and the requested core/integration suite.
Keep Chinese and English workflow, collaboration, and core documentation aligned with same-transaction projection, affected-row guards, bounded summaries, and direct-run/awaiting-input no-ops.
main gained 0045_agent_configuration_catalog, so the invocation reference migration moves to 0046; the migration READMEs list main's 0045 before it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained migrations 0046-0048, so the invocation reference migration moves to 0049. enqueueRun keeps main's task-conversation admission checks and enqueueAdmittedRun, and both now insert through enqueueRunWithInvocation so the workflow invocation reference is written with the IssueRun. The IssueRun type keeps both workflowInvocationRef and main's triggerEvidenceRefId. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main replaced the core and migration READMEs with package and migration maps, so the invocation linkage moves into that format: the workflow*.go row names the shared WorkflowRun creation, the invocation reference and the IssueRun status projection, and the migration map gains 0049 with its settlement and constraint notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on linkage main gained 0049_thread_commands_workflow_run, so the invocation reference migration moves to 0050 in the migration map. main's new session end deadline settles through finishWorkflowRun, so it projects onto the linked IssueRun like every other status write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in cloud#91 (Node-unreachable settlement of in-flight workflow sessions), cloud#100, cloud#104 (workflow globals and sys.*) and cloud#105. The only textual conflict was the import block of workflow_scheduler_db_test.go; advanceWorkflowRun now takes the deployment's unreachability window, so the projection rollback test passes store.DeliveryUnreachableAfter like AdvanceWorkflowRunsOnce does. The new Node-unreachable settlement goes through finishWorkflowRun -> settleWorkflowRunStates, so it projects onto the linked IssueRun as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in cloud#83, cloud#101, cloud#102 and cloud#107. cloud#101 adds 0050_personal_agent_configurations.sql, so the invocation-reference migration moves on to 0051_workflow_invocation_reference.sql: the file is renamed unchanged, the integration test's migration constant follows it, and both migration READMEs list 0050 and 0051 in order. main's new `delete(input, "agentConfiguration")` lands in enqueueRunWithInvocation, the single writer that both enqueueAdmittedRun and the workflow confirm path go through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in cloud#108 (workflow session turn deadline) and cloud#109 (workflow run cancellation). cloud#109 adds 0051_workflow_run_cancel.sql, so the invocation-reference migration moves on to 0052_workflow_invocation_reference.sql: the file is renamed unchanged, the integration test's migration constant follows it, and both migration READMEs list 0050, 0051 and 0052 in order. Every new settlement path (turn deadline, cancel-before-start, cancel of a running run) goes through finishWorkflowRun -> settleWorkflowRunStates, so each projects onto the linked IssueRun; a cancel before the claim projects queued -> cancelled without inventing a start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The migration directory now holds 54 SQL files (main's 53 plus 0052), so both READMEs say so. The 0052 entry also names `workflow_runs_id_tenant_uniq`, the unique (id, tenant_id) constraint that the composite invocation foreign key requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0052 added its own UNIQUE (id, tenant_id) on workflow_runs, which duplicates workflow_run_tenant_identity from 0044. The composite invocation foreign key resolves against that existing constraint, so 0052 only adds the reference constraints now; the migration README says where the target comes from instead of describing a second unique key. Co-Authored-By: Claude Code <noreply@anthropic.com>
Confirm used to compare the selected snapshot's fields with the live draft and refuse with 409 workflow_version_fields_changed. That made selecting an older version useless exactly when the draft had moved on, which is the ordinary state between an edit and its publish, and the rule had no specs basis. Confirm now validates the submission against the chosen snapshot's own descriptor, the same rule the manual run entry follows. The divergence helpers lose their only caller and are removed; the frontend drops the 409 hint, and the collaboration docs and the composer READMEs describe the version's own schema as the authority. Co-Authored-By: Claude Code <noreply@anthropic.com>
…ked IssueRun The scheduler-level projection tests covered the workflow row only. Two end to end cases now assert what the issue shows: a Confirm-created IssueRun cancelled before its first claim projects cancelled with no start and no run.started activity, and one whose Node goes unreachable projects failed with both timestamps and the same reason the run carries. Co-Authored-By: Claude Code <noreply@anthropic.com>
The component READMEs still listed the field-divergence test that the chosen-version validation replaced, and the snapshot-form comment still said the @ form relies on the confirm's divergence refusal. Co-Authored-By: Claude Code <noreply@anthropic.com>
wanglongan587
left a comment
There was a problem hiding this comment.
Request changes:PR 描述的 ADR 状态漏了本 PR 实际修改的 implemented ADR,specs 记录需要同步;另有一处默认表单与校验依据不一致
审的 commit:676f758b4d1d2fa30c90f96c72b1e05fea2274a1(base upstream/main = ff72afa0;CI 按此 head check、frontend 均 SUCCESS)。增量范围 6699540..676f758b:7e1feb9(0052 去重)、bc47149(按所选版本校验)、2464f75(两条投影用例)、cedfbfe(文档/注释)与合并 main 676f758b。本地 go build ./...、go run ./cmd/checkformat、go vet ./internal/core ./integration 通过;本地没有 PostgreSQL,集成测试以 CI 为准。
已核过、没有问题的部分
- owner 的放宽结论已实现且删得干净:
confirmInteraction只按snapshotFormDescriptor校验(internal/core/issue_interactions.go:144-148);全仓已无workflow_version_fields_changed、liveFormDescriptor、workflowFieldDivergence、judgedField(s)、refusedFields;前端 409 提示路径删除;中英协作文档与组件 README 已改为「所选版本自身的 schema 是唯一依据」。 - 0052 去重正确:
(workflow_invocation_ref, tenant_id)外键引用 0044 已建的workflow_run_tenant_identity(0052:11-15;0044_model_attempt_workflow_run.sql:18已是UNIQUE(id, tenant_id),integration/migration_upgrade_path_test.go:1283已有断言),迁移测试移除了重复索引的断言;两份迁移 README 都写 54 个 SQL 文件、0052 小节说明引用 0044 的约束,目录实际 54 个(main 53 + 1)。 - 两条 linked IssueRun 用例存在,断言与 PR 描述一致:开始前取消 → IssueRun
cancelled、started_at为 NULL、activity 恰为run.enqueued/run.cancelled;Node 不可达 →failed、带开始/完成时间与同一原因。 - 合并干净:
git merge-base cedfbfea ff72afa0 = 27e7f488,main 侧 20 个改动文件与本 PR 的 43 个文件无交集,676f758b没有改写 PR 自有代码(逐提交比对增量一致)。 - 交付物:43 个改动文件全是代码、测试、正式文档与模块 README,没有计划/进度记录;
git diff --check干净;提交按单一目的拆分,行为提交带测试。 - workflow 根 ADR 的
approved声明成立:specs6aad4f9上decisions/cloud/workflow/0-cloud-side-scheduler-owns-workflow-execution.md仍为approved,fixtures=false的真实 Node 联合验收未交付,本次不开把 ADR 改为implemented的 specs PR——这部分与描述一致。
需要修改(阻塞):PR 描述漏了 issue-collaboration 的 implemented ADR,specs 记录要跟着改
本 PR 删除的行为并不是「specs 里没有依据」。这条门禁写在 specs decisions/cloud/issue-collaboration/0-typed-targets-and-explicit-execution-intent.md(status: implemented,自 2026-10-17 ad5ed88 起,之后没有再改):
- D9(specs
upstream/main@6aad4f9的:204-205)明确要求:@Confirm 在活稿表单与所选版本对提交有影响的属性不一致时返回409 workflow_version_fields_changed并列出字段。本 PR 删的正是这条。 - 两个核心用例把本 PR 删除的测试当作证据:
test-cases/cloud/issue-collaboration/collaboration-interactions.md:173(必须成立:活稿与所选版本分歧时 409 并列出字段)与:185(义务「版本分歧拒绝」→TestWorkflowConfirmRefusesAVersionTheLiveFormHasLeft+TestWorkflowFieldDivergence,状态 Covered);test-cases/cloud/workflow/publish-and-start-admission.md:73、:75、:81、:86(触发、禁止结果、义务「输入按运行将执行的那一份文档校验」与直接证据都引用TestWorkflowConfirmRefusesAVersionTheLiveFormHasLeft)。
- 因此 PR 描述的「ADR 状态」只列 workflow ADR 是不完整的:没有写「按 owner 2026-10-20 结论修改 issue-collaboration 根 ADR 的 D9」,也没有承接 specs 更新;「未包含 / 待对齐」只承诺合并后为导入快照核心用例提 specs PR,覆盖不到这两个核心用例和 ADR 正文。owner 结论第 3 条也要求把「Confirm 按所选版本校验」写进对应核心用例。
请处理:
- PR 描述「ADR 状态」补上这条 ADR(
implemented,按 owner 结论修改 D9;ADR 保持implemented,正文与两个核心用例由 specs PR 更新),并写明承接:在 circus#374 下开 specs 运行(node scripts/cli.ts run 71,parent #71)或明确一个 specs issue。 - 合并前把 specs PR 开出,或在描述里写明它的范围和合入时机;内容至少包括:
- 在
decisions/cloud/issue-collaboration/新增日期 ADR(按 specs 的「部分决策被后续 ADR 修改时保持原状态、由后续 ADR 的『继承与修改』记录修改项」写法),把 D9 改为「Confirm 只按所选版本自身的 schema 校验,选择版本是可执行路径,不再比较活稿」; collaboration-interactions.md的「必须成立」与「版本分歧拒绝」证据行按新规则改写(证据换成TestWorkflowConfirmValidatesAgainstTheChosenVersion,或删除该义务);publish-and-start-admission.md的触发/义务/证据里去掉已删除的测试,补上新用例。
- 在
- 请把 owner 结论里「这条规则来自 #65、specs 里没有依据」也纠正给 owner:依据就在这条 implemented ADR 的 D9。放宽结论本身有效,但合并后不能留下与实现相反的
implemented规格承诺;这属于决策落地的记录,不改变 owner 的决定。
需要 owner 决定(建议合并前处理):默认版本的 @ 表单不是被校验的那份 schema
workflow-interaction-composer.tsx:403-406初次打开读活稿描述符(useFormDescriptor),只有用户实际切换版本(setSnapshotId)才读GET …/snapshots/:snapshotId/form;而版本选择器的默认值是最新已发布版本(internal/core/workflow_collaboration.go:441-446),seedValues会把它写进 values(workflow-interaction.test.tsx:406-433断言的就是这条路径)。用户不动选择器直接 Confirm 时,服务端按最新已发布版本校验(issue_interactions.go:144-148),前端显示的却是活稿字段。- 活稿在发布后又编辑过(owner 所述的普通状态)时,用户会被
required_field_missing/invalid_field_value拒绝,而前端现在只剩「确认失败,请重试。」(workflow-interaction-composer.tsx:380),重试解决不了;这正是被删除的门禁原先解释的路径。也可能按用户从未看过的字段报错,而collaboration-interactions.md的禁止结果本来就写着「按用户没见过的字段报错」。 - owner 结论的理由「前端也按所选版本渲染表单」目前只在显式切换版本后成立。建议二选一,并与 owner 确认:初次渲染就按默认选中的版本取表单(让渲染与校验同源),或取消默认预选、要求用户显式选择;若接受现状留作后续,请在描述里写明承接 issue,并把
issue_interactions.go:146-147、form_descriptor.go:373-377里「the form offers the selected version's fields」这类断言改成实际行为。
非阻塞 / 建议
assistWorkflow仍用活稿描述符(issue_interactions.go:169-179),而 confirm 已改为所选版本;选中旧版本后,旧版本声明而活稿已删的字段会让 assist 返回 400,而同样值 confirm 通过,issue_interactions.go:163-164的「same descriptor」注释也不再成立。生产 assist 未接入(503 assist_unavailable),但这是同一规则的第三条路径,建议一并按所选版本解析,或把注释改成实际行为。internal/core/workflow_collaboration.go:213-216的注释还写「Both descriptors a confirm compares are built from here」;门禁删除后 confirm 已不再比较两个描述符,建议同步改掉。2464f75的 Node 不可达用例用原始 SQL 造 linked IssueRun(workflow_run_session_db_test.go:901-933),覆盖的是 settle → projection,不是「创建/关联」链路;描述里称「端到端投影用例」时建议注明范围,或改用enqueueWorkflowInvocation/ confirm 造数据。- 删除的
TestWorkflowConfirmRefusesAVersionTheLiveFormHasLeft原本覆盖「未选版本 + 活稿与最新发布版本字段不同」的端到端路径;替换后的 confirm 用例都显式带snapshotId。如果保留活稿默认渲染(见上一节),建议补一条该路径的拒绝用例,避免新规则在这条路径上没有证据。
合并前提醒
- 迁移编号:main 当前最高
0051,本 PR 的0052无同号;但开放的 cloud#113(0052_legacy_workflow_issue_run_settlement)与 cloud#112(0052_agent_run_residual_reclamation)也带0052,先合者占号,合并前按memory/rules/migration-numbering.md再核一次(顺延需同步测试常量与中英 README)。旧 PR cloud#71 也仍开着,本 PR 确立后请 owner/作者关闭。 - 描述里「剩余部分由 circus#374 承接」:运行 issue 收尾会关闭,建议改成 parent circus#71(或写明与 #77 的验收协调),避免承接点随运行关闭而消失。
审过的 commit:676f758b4d1d2fa30c90f96c72b1e05fea2274a1
…napshot-confirm # Conflicts: # internal/core/migrations/README.en.md # internal/core/migrations/README.md
- 前端先读活稿只为定位平台版本选择器及其默认版本,确定默认发布版本后即按该版本读取
`GET /workflows/{wfid}/snapshots/{sid}/form` 渲染,切换版本同样重新读取,使显示字段与
Confirm 校验的 schema 始终来自同一版本;确认失败按 `params.field` 给出字段级提示。
- Assist 按同一版本描述判断提交值形状,与 Confirm、表单读取三方一致。
- 抽出 `workflowLatestSnapshot`,让「最新已发布版本」只有一个定义。
- 迁移改为 `0053_workflow_invocation_reference.sql`,避让上游已合入的 0052;0052 的升级测试
拆成「仅 0052」与「整条序列」两段断言,后者覆盖 0053 结算剩余未链接的非终态行。
Co-Authored-By: Claude Code <noreply@anthropic.com>
wanglongan587
left a comment
There was a problem hiding this comment.
复审 cloud#114 @ c9a6a48c:行为实现没问题,但这个 head 早于 main 解冻,迁移编号已与 main 同号且 PR 冲突
审过的 commit:c9a6a48c3e82517d5faa04df05ed550f593a3a58(base 9814cacd)。CI 按此 head:check 11m9s、frontend 3m44s,均 SUCCESS。增量 676f758b..c9a6a48c 是合入 main(09b9fe47,combined diff 只有迁移改名与两份迁移 README 三处解冲突)加 c9a6a48c 一个提交。
已核,没有问题的部分
- 选项 A 的默认视图:
useChosenVersionForm只用活稿定位平台版本选择器及其默认版本,之后默认与切换都走同一条GET …/snapshots/:id/form;seedValues用选中的 snapshot id 覆盖选择器值,key变化时表单按新版本重建,不会退回「默认值 = 最新版本」。默认视图里不再出现活稿未发布的字段,显示字段与 Confirm 校验的 schema 同源。 - assist 与 Confirm 同源:
assistWorkflow先workflowVersionCursor取版本,再snapshotFormDescriptor;没有发布版本时才回落到活稿。先算游标、再foldLegacyVersionAnswer的顺序是对的(旧version键要先被游标读到),我特意核了这一点。 - 字段级拒绝提示:
required_field_missing/invalid_field_value在后端都带Fault.params.field(form_descriptor.go:191/199/204、workflow_admission.go:83/100/105),前端按渲染版本的 descriptor 取 label,取不到时退回键名,其他 code 仍是通用提示。 - 迁移语义与 0052 的先后:0052 只结算 queued / dispatched,本 PR 的迁移结算 running、deferred 与软删除的 queued,以及无条件 VALIDATE 的 CHECK;升级测试拆成「仅 0052」与「整条序列」两段,覆盖合理。
- 文档与用例:中英协作文档、
workflow_collaboration.go注释、组件 README 都改成「默认视图也按所选发布版本渲染」,与实现一致;用例覆盖默认视图看到 v3 独有字段、Confirm 带 v3 的snapshotId通过、invalid_field_value显示字段名、assist 在 v1 下拒绝活稿的scope。
阻塞
- PR 与当前 main 冲突。
mergeable=CONFLICTING;git merge-tree upstream/main c9a6a48c的冲突只在internal/core/migrations/README.md与README.en.md——main 的 #183(mergea9c5f20)在 02:27Z 合入,新增了 0053 / 0054 两行。请合入当前 main 后重推。 - 迁移编号已与 main 同号。 main 现有
0053_retire_tenant_level_clones.sql、0054_git_delivery_refs.sql(共 56 个 SQL 文件),本 PR 的0053_workflow_invocation_reference.sql既同号又小于最新编号,违反memory/rules/migration-numbering.md。请改名为0055_workflow_invocation_reference.sql,并同步:integration/workflow_invocation_migration_test.go:10的workflowInvocationMigration;integration/migration_upgrade_path_test.go:1436 / 1452 / 1480的注释与失败信息里的 0053;- 两份迁移 README 的表行与说明段(
README.md:67 / :83、README.en.md:67 / :83); - PR 描述里的编号(「迁移编号改为 0053」「迁移
0053_…」以及「未包含 / 待对齐」里的顺延说明)。
- 两份迁移 README 的文件计数已经是错的。 head 的迁移目录实际有 55 个 SQL 文件(main 的 0052 + 本 PR 的一个),两份 README 仍写
54——09b9fe4解冲突时保留了 54。合入当前 main 后应为 57(56 + 1),表行也应有 57 条。顺带说明:main 自己现在也是 56 个文件却写 54(#183 没改这句),本 PR 既然要改这两份文件,请写真实数字。 - 在新 head 上重跑 CI。 现在的
check/frontend是9814cacd基线上的结果;#183 删掉了租户级 clone、Revision 改走 Git 交付 ref,虽然没直接改issue_runs/workflow_runs,合并后的树仍要重新验证(CI,以及task check的 PostgreSQL 集成测试)。
非阻塞(请确认接受,还是另开承接)
- 旧版本自己声明
snapshotId时,显示与执行仍可能不同源。 场景:活稿没有声明snapshotId(平台选择器在),但某个旧版本的图把它声明成自己的文本变量,或选项不全属于已发布 id 的 select。用户在默认视图切到该版本后,渲染的是该快照的表单,里面没有平台选择器,platformVersionField认不出,versionField为空、也不再 seedsnapshotId;Confirm 时workflowVersionCursor按活稿判定这个键归平台,于是提交里作者字段的值被当成版本游标——空值落到最新版本,非 id 值 404。结果是「看到旧版本的字段、跑的是最新版本(或被拒)」。这是 #71 既有规则(游标按活稿判定)与选项 A(默认与切换都按快照渲染)的交界;要么切到没有平台选择器的版本时不允许切(或给出提示),要么在组件 README 与用例里写明这一情况的预期行为。不在本 PR 阻塞,但请给个结论,避免合并后才发现。 formBlocker让任一读取失败都挡掉整个表单、切换版本期间整块换成Skeleton,都是既有行为,本次不阻塞。
合并顺序
不变:specs#164(46a43ff,开放)先合,本 PR 再合,最后由另一个运行把新 ADR 改成 implemented。specs#164 我在 circus#395 单列 review,不在本 PR 的结论里。
main 已合入 0052_legacy_workflow_issue_run_settlement、0053_retire_tenant_level_clones 与 0054_git_delivery_refs,按先合先得把本分支的迁移改名为 0055_workflow_invocation_reference.sql,并同步迁移测试常量、升级测试说明与两份迁移 README 的序号表和文件数(57)。冲突只在这两份 README。 Co-Authored-By: Claude Code <noreply@anthropic.com>
缺必填字段与提交未声明字段的拒绝原本只断言错误码;补上 issue_runs 计数不变, 以及同一 interaction 随后按所选版本重新确认成功,与 specs 用例的证据说明对齐。 Co-Authored-By: Claude Code <noreply@anthropic.com>
活稿自行声明 snapshotId(或用启动字段移除它)时没有选择器,表单按活稿渲染而 Confirm 执行最新已发布版本;切到的版本自己声明该键时该版本表单同样没有平台字段, 作者值会被当成版本游标。两份协作文档与组件 README 按实际行为写明这是作者占用 保留键的边界,是否禁止或提示这类切换留给 circus#405。 Co-Authored-By: Claude Code <noreply@anthropic.com>
wanglongan587
left a comment
There was a problem hiding this comment.
复审 @ 994fcbae:Approve
- 审过的 commit:
994fcbae0e88b95c7a223057d8f6abb6836b2222(basea9c5f20;当前upstream/main = 4128d86)。CI 按此 head:check12m49s、frontend2m14s,均 SUCCESS。 - 增量
c9a6a48..994fcbae三个提交:2cedb05(合 main、迁移顺延 0055)、3f09884(两条拒绝路径补断言)、994fcbae(保留键边界文档)。本次不改变行为:版本游标、Confirm、投影、前端表单与 assist 的实现代码与c9a6a48一致,0055 与上一轮审过的 0053 内容逐字相同,只改了文件名。
上一轮阻塞项
- 合 main:已合
a9c5f20,冲突只在两份迁移 README;main的 0052–0054 表行与说明段都保留。与 main 同时改过的另外 10 个文件逐个核对过,diff 只含本 PR 的改动,没有回退 main 的内容。 - 迁移顺延 0055:main 当前仍是 56 个 SQL、最高
0054;本 PR 57 个、编号0055,无同号。workflowInvocationMigration常量、migration_upgrade_path_test.go的 0055 措辞与失败信息、中英 README 的表行和说明段都已同步。 - README 文件数:head 的表行 57 行,与目录 57 个 SQL 文件逐一对上,无重复、无缺行;正文写 57。main 原来的 54 确实是过期数字(实际 56),这里顺带纠正,符合复审判定。
- CI:新 head 上
check/frontend均 SUCCESS。
本轮新增内容
TestWorkflowConfirmValidatesAgainstTheChosenVersion:缺必填字段与提交未声明字段两条拒绝路径都断言issue_runs计数不变,并各用同一个 interaction 按所选版本重新确认成功;assist 对同一版本的判断也在同一用例里。与 specs 复审要求的「两条路径都断言」一致。TestMigration0052LegacyWorkflowIssueRunSettlementAppliesFreshAndUpgrades拆成「仅 0052」与「整条序列」两段:前者保留 0052 自己的效果,后者固定 0055 对running、deferred、软删除queued的追加结算,以及终态行与 agent 行不动;migratedTwice覆盖重复应用。- 保留键边界的文档与实际行为一致:活稿声明
snapshotId时workflowVersionCursor返回空、执行最新已发布版本;切到的版本自己声明该键时该版本快照表单不含平台字段,而 Confirm 仍按活稿判定游标,空值落最新、非 id 走 404。前端「文本变量」与「含未发布选项的 select」两条用例固定同名变量仍是普通输入。这类切换是否禁止/提示由 circus#405(type:decision)承接,本 PR 不改变行为,合理。
本地核对(含当前 main 的合并树)
head 比当前 main 少一个合并(#118 的 Node unknown window,4128d86)。我本地把 upstream/main 合进 994fcbae:无冲突(workflow_run_session_db_test.go 自动合并),go build ./...、go vet ./internal/core ./integration、版本游标与 launch key 的单元测试均通过。#118 去掉了 runWorkspaceNodeUnknown 的 connected 条件、并让 disconnect 不刷新 last_seen_at;本 PR 的 TestWorkflowSessionNodeUnreachableProjectsLinkedIssueRun 直接把 last_seen_at 老化 2 小时,语义不受影响。CI 只覆盖 a9c5f20 基线;GitHub 现在报 CLEAN,如需严格按合并树再跑一遍 CI,可再合一次 main,但我不认为这是阻塞。
合并前提醒(非阻塞)
- 迁移号
0055目前还有 cloud#112、#117、#119 也在排,先合者占号;若它们中有先合并的,请按memory/rules/migration-numbering.md顺延并同步常量与两份 README。 - 合并顺序按 owner 结论:specs#164(新 ADR
approved)先、cloud#114 后、再由另一个运行把新 ADR 改implemented;specs#164 的 review 在 circus#395,不在本 PR。 - PR 只含交付物(49 个文件:代码、测试、迁移、正式文档与模块 README),没有过程记录;
git diff --check干净。 - 合并后
fixtures=false的真实 Node 联合验收与 specs 证据 PR 仍未完成(描述已写明由 circus#71 承接),所以本 PR 合并不等于运行 circus#374 收尾。
ADR 状态
描述里的三条都成立:修改的 implemented ADR D9 由 specs#164 承接;实现的 approved ADR 写明剩余部分由 circus#71 承接;不涉及的 ADR 有说明。可以按 owner 定的顺序合并。
cloud#119 先合并并占用了 0055,把调用引用迁移顺延到 0056,并同步 workflowInvocationMigration 常量、迁移测试的措辞与失败信息,以及两份 迁移 README 的表行、说明段和文件计数(58)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
wanglongan587
left a comment
There was a problem hiding this comment.
Request changes:迁移 0055 与 main df9799c 撞号,顺延后再审增量
审过的 commit:994fcbae0e88b95c7a223057d8f6abb6836b2222。这个 head 的内容与我 2026-10-21 03:28Z 的 Approve 相比没有变化,下面只有合并前的环境与编号阻塞,没有新的行为问题。
阻塞:编号与 main 同号
upstream/main已是df9799c5178db669337e39b6a5bb014628c93c34(cloud#119 / #377 合入),新增0055_workflow_run_workspace_release.sql,共 57 个 SQL 文件,最高编号0055。- 本 PR 也是
0055_workflow_invocation_reference.sql(57 个文件)。按memory/rules/migration-numbering.md,合并前必须接在 main 最新编号之后,不能与 main 同号或更小。 - PR 现在是
CONFLICTING。我本地用git merge-tree把upstream/main合进994fcbae核对:冲突只有internal/core/migrations/README.md与README.en.md(两边都在序号表里加了 0055 行),其余文件(含migration_upgrade_path_test.go、workflow_scheduler.go、中英文工作流文档)都能自动合并。撞号是唯一阻塞,和上一轮同性质。
请在新 head 一起做
- 追平
upstream/main,迁移改名为0056_workflow_invocation_reference.sql(当前 main 最高 0055)。 - 同步引用:
integration/workflow_invocation_migration_test.go:10的workflowInvocationMigration常量;integration/migration_upgrade_path_test.go:1477、:1493、:1521的 0055 措辞与失败信息;internal/core/migrations/README.md/README.en.md的序号表行、说明段(第 69、87 行附近)和文件计数(合入 main 后应为 58)。 - 合并后保留 main 的 0055 表行与说明段,顺序为 0055(workspace release)→ 0056(本 PR)。
- PR 描述同步:迁移编号 0056、文件数 58;「ADR 状态」里 specs#164 已合并(merge commit
c9e99a9f3670f831860f91a843d07658669cf4c7,新 ADR 以approved落到 specs main),把「specs#164 先合入」改成既成事实,并保留「合并后由另一个运行把新 ADR 改为implemented、证据升Covered」这一条。 - 新 head 上重跑 CI(
check/frontend与 PostgreSQL 集成测试),然后打status:in-review请我复审这段增量。
提醒:0056 也可能被先占
当前开放的 cloud#116 已带 0056_agent_run_controlled_reclamation.sql,cloud#117 / #112 也还带 0055;先合者占号。合并前按当时 main 的最新编号再核一次,若被别人先占,顺延到再下一个号并同步上面四处引用。
维持上一轮结论的部分(不需要返工)
默认视图与切换版本同源读取、assist 与 Confirm 同源、字段级拒绝提示、两条拒绝路径的断言、TestMigration0052… 拆段与 0055 的追加结算断言、保留键边界文档,我都已核过。0056 的 SQL 内容应与 0055 逐字相同,我只核对改名、计数与描述。
wanglongan587
left a comment
There was a problem hiding this comment.
Approve:增量只有迁移顺延与计数,29f0532e 可以合并
- 审过的 commit:
29f0532e788502ef9e81c7b5421633fcdab8412a(basedf9799c5178db669337e39b6a5bb014628c93c34,即当前upstream/main)。CI 按此 head:check12m19s、frontend3m47s,均 pass。 - 增量
994fcbae..29f0532e只有一个合并提交29f0532e(两个父:旧 head994fcbae+ maindf9799c);PR 自身相对新 main 仍是 49 个文件,文件集合与上一轮逐一对上,只多了迁移改名。
核对结果
- 迁移编号:main
df9799c57 个 SQL、最高0055_workflow_run_workspace_release.sql;head 58 个、最高0056_workflow_invocation_reference.sql,无同号,接在最新编号之后。 - 改名是纯改名:
0056_workflow_invocation_reference.sql与上一轮0055的 blob 都是9130fae6,内容逐字未变。 - README 冲突解决正确:中英两份都保留 main 的 0055 表行与说明段,在其后追加 0056 表行与说明段,文件计数改为 58(main 57 + 1);表格行与
ls-tree的 58 个 SQL 文件对得上。 - 引用同步:
workflowInvocationMigration常量、migration_upgrade_path_test.go的三处 0055 措辞与失败信息都改为 0056;head 上已无「0055 + invocation」的残留。 - 合并没有丢 main 的改动:我用
git merge-tree重算了自动合并树,实际 head 与它只差两份冲突的 README(人工解决)和两个测试文件的 0055→0056 字样;其余 main 与 PR 同时改过的文件(workflow_scheduler.go、workflow_run_session.go、docs/workflows等)都是标准三方合并结果,#118 / #119 的改动与 PR 的改动都在。 - 仓库卫生:无冲突标记;
git diff --check df9799c..29f0532e干净;49 个改动文件仍是代码、测试、正式文档与模块 README,没有过程记录。 - ADR 状态:根 ADR
0-typed-targets-and-explicit-execution-intent.md仍implemented并带历史决策提示;新 ADR20261020-confirm-validates-against-the-chosen-version.md在 specs mainc9e99a9f(specs#164 已合并)为approved,由后续运行改implemented并把证据升Covered;0-cloud-side-scheduler-owns-workflow-execution.md仍approved,剩余fixtures=false联合验收由 parent circus#71 承接。三条与 owner 的合并顺序一致。
Refs ora-space/circus#374
Issue 里 @ Workflow 点 Confirm 时,按所选发布版本创建唯一的 WorkflowRun,并与 IssueRun 稳定关联;直接启动、manual workflow IssueRun 和 @ 确认走同一个创建函数
insertWorkflowRun;调度器推进 WorkflowRun 时,状态同步投影回关联的 IssueRun。已适配 main 上的运行身份与真实会话执行。fixtures=false的真实 Node 联合验收仍未交付,补做时与 circus#77 的验收范围协调。相对
6699540的增量409 workflow_version_fields_changed门禁,liveFormDescriptor、workflowFieldDivergence失去唯一调用点后一并删除;前端删除对应提示路径,协作文档与组件 README 改为「所选版本自身的 schema 是唯一依据」。集成测试改为:选字段与活稿不同的旧版本可按该版本通过;缺该版本必填字段返回required_field_missing;提交该版本未声明的字段返回invalid_field_value。GET …/snapshots/:snapshotId/form渲染,切换版本按新版本重新读取。显示的字段与 Confirm 校验的 schema 因此始终来自同一版本,活稿里未发布的字段不再出现在默认视图。新增TestWorkflowInteractionComposer用例:默认视图显示最新发布版本的字段,Confirm 提交带该snapshotId并通过。同步做的非阻塞项:assist 按同一版本描述判断提交值;workflow_collaboration.go的过期注释更新;faultParam让 Confirm 的字段拒绝提示具体字段名(请填写必填项:<label>、字段「<label>」的取值不合法…)。df9799c5后,cloud#119 先合并并占用了0055_workflow_run_workspace_release.sql,按「先合先得」本 PR 的迁移顺延为0056_workflow_invocation_reference.sql,两份迁移 README 的序号表与文件数(58)同步更新。与 0052 的顺序仍有意义:0052 只结算queued/dispatched,0056 结算其余未链接的非终态行(running、deferred、以及软删除的queued),因为 0056 的 CHECK 无条件 VALIDATE 要求所有非终态 workflow 行带引用。TestMigration0052LegacyWorkflowIssueRunSettlementAppliesFreshAndUpgrades因此拆成两段断言:仅应用 0052 时它自己的效果不变;应用完整序列后固定 0056 的追加结算。workflow_run_tenant_identity,两份迁移 README 同步。fixtures=false的联合验收。Confirm 建立关联后、下一轮调度前取消 → IssueRuncancelled、started_at为 NULL、没有run.started;Node 不可达 → IssueRunfailed、带上运行的时间戳与原因。df9799c5合并进分支,冲突只在两份迁移 README:main 的0055_workflow_run_workspace_release.sql保留,本 PR 的迁移顺延为0056,文件数更新为 58。改动
issue_interactions.go、issue_runs.go、workflow_invocation.go):同一事务里依次做准入(admitWorkflowRun)→insertWorkflowRun建运行(snapshot_id取准入解析出的版本,input只放 Start 变量和全局变量)→ 冻结调用者的执行器槽位绑定 → 入队 IssueRun 并写workflow_invocation_ref→ 原有的run_id IS NULL比较交换。三个入口的共用创建函数统一冻结created_by、project_id、plugin_version。Confirm/manual 的创建人取已验证的调用者,Project 取 Issue 的project_ref;直接启动取请求体projectId。三种入口均要求合法可访问的 Project,缺失返回409 workflow_run_project_required。Confirm/manual 不通过开发模拟器伪造执行或跳过槽位绑定。snapshotFormDescriptor),不再读取活稿比较字段;选旧版本就是按那份快照的表单执行。workflowLatestSnapshot统一「最新已发布版本」的定义,运行解析与 assist 共用。issue_interactions.go):提交里带发布版本(或工作流已有发布版本)时,按该版本的描述校验提交值的形状;没有发布版本时仍用活稿描述——这是这类工作流唯一能显示的表单,也是 Confirm 会拒绝运行的草稿。workflow-interaction-composer.tsx):活稿只用于定位版本选择器与默认版本;useChosenVersionForm把三个读取(活稿、已发布快照列表、所选版本表单)归约为 loading / failed / ready 三态,默认版本与切换后的版本走同一条读取路径。faultParam读取后端Fault.params.field,把required_field_missing/invalid_field_value映射成带字段名的提示。issue_run_lifecycle.go),避免同一次调用出现两份互不相关的执行结果。0056_workflow_invocation_reference.sql:先把没有引用、仍未终结的历史 workflow IssueRun 结算为 failed(workflow_execution_unavailable_before_invocation_linkage),再加(workflow_invocation_ref, tenant_id)→workflow_runs(id, tenant_id)复合外键(引用 0044 的workflow_run_tenant_identity)、引用唯一、执行器类型与引用一致的 CHECK(历史终态行放行),无条件 VALIDATE。编号接在 main 已有的0055_workflow_run_workspace_release.sql之后(与 0052 的先后仍有意义,见上);该列此前没有写入者,迁移注释里写明。projectWorkflowRunToIssueRun(workflow_issue_projection.go):在调用方事务里把 WorkflowRun 状态映射到 IssueRun(pending→queued、running→running、succeeded→completed、failed→failed、cancelled→cancelled),写run.started/run.message/ 终态 system activity,不冒充 workflow 评论;awaiting_input不改 IssueRun。workflow_scheduler.go、workflow_run_session.go、workflow_run.go):认领pending→running的比较交换成功后(同一 UPDATE 已写started_at)调用投影;settleWorkflowRunStates只在更新到行时投影,终态结算同一 UPDATE 先写finished_at。会话完成、失败、启动超时、Node 不可达、取消都经过这个结算。摘要由调用方workflowIssueOutputSummary从输出节点结果或运行错误生成,按 UTF-8 字符边界截到 ≤20000 字节。snapshotId字段当平台版本选择器。docs/collaboration/interactions(中英)、internal/core与迁移目录中英 README、组件与lib中英 README。两份文档与组件 README 都写明版本选择器的前提:活稿自行声明保留键snapshotId(或用启动字段移除它)时平台没有选择器,@表单按活稿渲染、Confirm 仍按活稿的版本规则执行最新已发布版本,此时显示的字段可能属于另一个版本;这是作者占用保留键的边界,不是可依赖的配置,作者不得把snapshotId用作自己的变量名。选择器存在、但切到的版本自己的图声明了该键时,该版本文档同样写明:该版本表单没有平台字段,Confirm 仍按活稿判定版本,作者值会被当成版本游标。是否在切换时禁止或提示这类版本,已开 circus#405 等 owner 决定,本 PR 不改变这一行为(已知不精确:活稿把它声明为选项全属于已发布 id 的 select 时前端仍会当选择器;owner 已在 circus#405 选定方案 D,由发布门禁消除这个边界,四份文档在承接运行 circus#411 中对齐)。测试
integration/workflow_interaction_test.go:TestWorkflowConfirmValidatesAgainstTheChosenVersion(选字段与活稿不同的旧版本按旧 schema 通过;缺旧版本必填字段required_field_missing;提交旧版本未声明的字段invalid_field_value;assist 对同一版本做同样的判断)。两条拒绝路径现在都断言issue_runs计数不变,并且同一 interaction 随后按该版本重新确认成功,与 specs 用例里的证据说明一一对应。integration/workflow_invocation_test.go:未发布、跨 workflow / 跨租户 snapshot、非法输入都不留下任何行;选旧版本后编辑并重新发布,运行的 snapshot / input / definition 不变;同 Idempotency-Key 重放、换 key 409、并发两个 Confirm 只建一个 WorkflowRun 和一个 IssueRun;manual 路径同样关联;TestWorkflowConfirmSchedulerProjectsCancelBeforeStart断言开始前取消投影为cancelled、无run.started。internal/core/workflow_run_session_db_test.go:TestWorkflowSessionNodeUnreachableProjectsLinkedIssueRun(Node 不可达结算为 failed 后,关联 IssueRun 投影 failed,带 start/finish 与同一原因)。integration/workflow_invocation_migration_test.go:空库与带历史数据升级(含 0052 之后的前一 schema)两种情况下约束都已 VALIDATE;四种非终态结算、三种终态保留、迁移重放、历史行之后仍可 UPDATE、新的无引用 workflow IssueRun 被拒。integration/migration_upgrade_path_test.go:0052 的升级测试拆成「仅 0052」与「整条序列」两段,后者固定 0056 对running与软删除行的追加结算。integration/workflow_invocation_identity_test.go:Confirm/manual 冻结实际调用者、Issue Project、插件版本与逐槽位模型绑定;后续配置变化不改写冻结值;开发模拟器不能使这两个入口跳过绑定。workflow-interaction.test.tsx:默认视图显示最新发布版本的字段并用该snapshotId确认;invalid_field_value显示带字段名的提示;重试提示改为请填写必填项:<label>;版本切换重取表单、单 interaction、失败重试复用、状态与详情链接、历史行回退(共 19 个用例)。29f0532e的验证:task check(REQUIRE_POSTGRES=1,含 PostgreSQL 集成测试)通过;task frontend:check通过(生成客户端无漂移、140 个测试文件 / 1204 个用例);此前 head 上go test -race ./integration/ -run Workflow与go test -race ./internal/core/ -run 'Workflow|Interaction|Form'通过,本次增量只改迁移编号、测试措辞与文档,未再重跑 race;CI 的check与frontend两个任务在29f0532e上均通过。ADR 状态
ora-space/specs的decisions/cloud/issue-collaboration/0-typed-targets-and-explicit-execution-intent.md(implemented)D9。owner 2026-10-20 的结论删除了「活稿与所选版本不一致时返回409 workflow_version_fields_changed」;specs 侧由新增日期 ADR20261020-confirm-validates-against-the-chosen-version.md(当前approved)在「继承与修改」中修改 D9 的校验依据,根 ADR 保持implemented并在标题下加历史决策提示。对应 specs PR ora-space/specs#164(运行 circus#395)。approved,证据当时按Missing/Partial记录),本 PR 再合入,最后由另一个运行把新 ADR 改为implemented并把证据升为Covered。ora-space/specs的decisions/cloud/workflow/0-cloud-side-scheduler-owns-workflow-execution.md(approved)D3 中的创建与引用部分:三种入口共用启动函数;创建事务里冻结created_by/project_id/plugin_version/ 逐槽位绑定;workflow_invocation_ref的租户一致复合外键、唯一引用与 CHECK;存量非终态 workflow IssueRun 的一次性结算。fixtures=false的真实 Node 联合验收仍未交付。根 ADR 保持approved,剩余部分由 parent circus#71(及其运行 circus#374)承接(circus#70 已关闭),本次不开把 ADR 改为implemented的 specs PR。decisions/cloud/workflow/20261015-executor-slots-replace-agent-model-ids.md(approved)的槽位语义本次未改,只在创建时按既有语义冻结绑定;decisions/cloud/workflow/20261016-asset-import-and-published-snapshots.md已是implemented,本次只补导入快照经@Confirm 与直接运行的调用证据。未包含 / 待对齐
fixtures=false部署上的真实 Agent 节点验收:circus#70 已关闭,仍未补做;补做时与 circus#77 的验收范围协调,不重复建设。🤖 Generated with Claude Code