Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9e0fcb2
fix(core): preserve null workflow run timestamps
creegon2 Oct 16, 2026
6c92e30
feat(core): bind workflow confirms to real run records
creegon2 Oct 16, 2026
7c63e82
feat(core): project workflow states into issue activity
creegon2 Oct 16, 2026
006078c
feat(frontend): link confirmed workflow runs and reuse interactions
creegon2 Oct 16, 2026
09b75af
fix(workflow): address invocation migration and form review
creegon2 Oct 17, 2026
7845870
test(frontend): share the field-divergence confirm setup
creegon2 Oct 17, 2026
706d77c
merge: sync main (session dashboard) into invocation linkage
creegon2 Oct 17, 2026
ea57c3b
merge: sync workflow asset import for invocation evidence
creegon2 Oct 17, 2026
7088e26
test(workflow): cover imported snapshots in issue invocations
creegon2 Oct 17, 2026
af0ee56
style(test): format the imported snapshot invocation test
creegon2 Oct 17, 2026
1955dbb
merge: sync main (git-native bindings, ADR citations) into invocation…
creegon2 Oct 17, 2026
9c7b396
merge: sync main (docs rebuild, workflow scheduler) into invocation l…
creegon2 Oct 18, 2026
ecd2589
merge: sync main (blocked Workspace abandonment) into invocation linkage
creegon2 Oct 18, 2026
52314da
docs(workflow): 修正快照表单来源与字段差异限制
creegon2 Oct 19, 2026
889a84d
merge(workflow): 合并 main 并保留调用创建的完整运行身份
creegon2 Oct 19, 2026
3914c49
merge(workflow): 合并外部 Git 主线并顺延调用引用迁移
creegon2 Oct 19, 2026
1ae5e42
merge: sync main (condition parity, model attempts, slot route fix) i…
creegon2 Oct 20, 2026
713b191
feat(core): project workflow run states onto linked issue runs
creegon2 Oct 20, 2026
fb34d82
docs(workflows): describe linked issue run status projection
creegon2 Oct 20, 2026
f833135
merge: sync main (agent declaration catalog) into invocation linkage
creegon2 Oct 20, 2026
ebb93cd
merge: sync main (task conversation) into invocation linkage
creegon2 Oct 20, 2026
07136b0
merge: sync main (module README maps) into invocation linkage
creegon2 Oct 20, 2026
140849f
merge: sync main (workflow session end, run statistics) into invocati…
creegon2 Oct 20, 2026
a302856
Merge upstream main into feat/158-workflow-snapshot-confirm
creegon2 Oct 20, 2026
8d1f3c7
Merge upstream main into feat/158-workflow-snapshot-confirm
creegon2 Oct 20, 2026
08102c2
Merge upstream main into feat/158-workflow-snapshot-confirm
creegon2 Oct 20, 2026
6699540
docs(migrations): count 0052 and name its tenant-unique prerequisite
creegon2 Oct 20, 2026
7e1feb9
fix(core): reference the tenant identity 0044 already enforces
Zpc001 Oct 20, 2026
bc47149
fix(core): validate a workflow confirm against the chosen version alone
Zpc001 Oct 20, 2026
2464f75
test(core): project cancellation and an unreachable Node onto the lin…
Zpc001 Oct 20, 2026
cedfbfe
docs: drop the last references to the removed divergence gate
Zpc001 Oct 20, 2026
676f758
Merge remote-tracking branch 'upstream/main' into feat/374-workflow-s…
Zpc001 Oct 20, 2026
09b9fe4
Merge remote-tracking branch 'upstream/main' into feat/374-workflow-s…
Zpc001 Oct 21, 2026
c9a6a48
feat(core): @ 表单默认视图与 Confirm 同源读取所选发布版本
Zpc001 Oct 21, 2026
2cedb05
chore: 追平 upstream/main a9c5f20,迁移顺延为 0055
Zpc001 Oct 21, 2026
3f09884
test(core): 两条版本拒绝路径都断言不建运行并可重新确认
Zpc001 Oct 21, 2026
994fcba
docs: 写明没有平台版本选择器时的显示与执行边界
Zpc001 Oct 21, 2026
29f0532
chore: 追平 upstream/main df9799c5,迁移顺延为 0056
Zpc001 Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions docs/collaboration/interactions.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,34 +69,55 @@ Cloud's own background loops, see [Agent IssueRuns](../agent-runs/issue-runs.en.
A workflow has exactly one execution boundary: an explicit **confirm**.

```text
select workflow ──▶ read form descriptor ──▶ fill in (optional AI assist) ──▶ confirm ──▶ IssueRun
select workflow ──▶ read form descriptor ──▶ fill in (optional AI assist) ──▶ confirm ──▶ WorkflowRun + IssueRun
no execution no execution no execution, no run the only execution boundary
```

- The **form descriptor** (`GET /tenants/:tid/collaboration/forms/:formRef?issueId=…`) is an
Issue-facing rendering description: `formRef`, title, description and a list of fields. It is not the
workflow's schema and binds to no schema technology. Fields come in three bands: platform fields, the
variables declared by the workflow's Start node, and the graph's global variables.
The frontend first reads the current document only to locate the platform version selector and its
default version. Once the default (the newest published version) is known, the initial form is read
from that version through `GET /tenants/:tid/workflows/:wfid/snapshots/:snapshotId/form?issueId=…`,
and switching the selector reloads the new version the same way. The form shown and the version Confirm
validates are therefore always the same, and fields the live draft has not published never appear in
the default view.
That promise assumes the platform can name the version, and the selector itself comes from the live
draft. When the live draft declares the reserved key `snapshotId` (or removes it through a launch
field) there is no version to choose: the @ form renders the live draft while Confirm still applies
the live draft's version rule and runs the newest published version. When the selector does exist but
the chosen version's own graph declares `snapshotId` (or its select offers options that are not all
published ids), that version's snapshot form has no platform field while Confirm still treats the key
as the platform's: the author's value is read as a version cursor, so an empty value runs the newest
published version and a non-id value is refused. Both are the boundary of an author occupying a
reserved key, not a supported configuration: authors must not name their own variables `snapshotId`.
Whether switching to such a version should be refused or warned about is a later decision (circus#405).
- **Platform fields** are injected only when the form is opened for an Issue: repository URL and branch
(defaulting to the repository and default branch of the Issue's Project; required), a prompt, the
published version to run `snapshotId`, and additional context references `context_refs`. When the
workflow declares a variable of the same name, the workflow's declaration wins.
- **AI assist** (`POST .../issues/:iid/collaboration/assist`) only returns a field-level suggestion patch
and suggested context references: it never creates a run and never writes persistent context
references. Applying a suggestion is the user's action. Production has no assist provider wired in and
returns `503 assist_unavailable`.
references. Applying a suggestion is the user's action. It judges the shape of the submission against
the same version descriptor Confirm uses, so once anything is published it accepts only the fields
that version declares. Production has no assist provider wired in and returns `503 assist_unavailable`.
- **Confirm** (`POST .../issues/:iid/interactions/:ixid/confirm`) is the only transition that turns a
workflow interaction into execution intent:
- Frontend validation is only for user experience. On confirm Cloud resolves the target **again** and
validates the submitted values against the form of **the published version being confirmed**; a
workflow with no published version cannot be confirmed, because an unpublished draft cannot run.
- The form the user saw comes from the current document; if its fields no longer match the confirmed
version's, the confirm is refused with the changed fields listed
(`409 workflow_version_fields_changed`), rather than validating the user's values against a schema
they never saw.
- Cloud validates against the selected published version's own fields alone, no longer comparing
them with the live draft. Choosing an older version runs that snapshot: a submission that matches
its form is accepted even when its fields differ from the current draft, and a missing required
field or a field that version never declared is refused by that version's schema.
- The submitted values are kept as the interaction's input and the executed version is frozen into the
run input, so the run still knows which version it executed after the workflow is edited or
published again.
- One WorkflowRun and one IssueRun are created in the same transaction, and the IssueRun's
`workflowInvocationRef` points at that WorkflowRun (a unique composite foreign key with the tenant).
Direct runs, manual workflow IssueRuns and confirm share one creation function. A linked IssueRun is
never handed to `ExecutionDispatcher`.
- The interaction is claimed with a compare-and-set on `run_id IS NULL`; a repeated confirm returns
`409 interaction_already_confirmed`, and confirming a non-form interaction returns
`409 interaction_not_confirmable`.
Expand All @@ -112,12 +133,18 @@ defining what those capabilities are internally:
| `FormDescriptorProvider` | Produce the form descriptor for a `formRef` | Forms projected from workflow documents |
| `InputAssistProvider` | AI assist for forms | Not wired (503) |
| `ContextBuilder` | Assemble a run's context snapshot | Not wired; falls back to a deterministic snapshot of task, form values, target and raw context references |
| `ExecutionDispatcher`, `ExecutionObserver` | Hand queued runs to an external executor and observe their progress | Not wired: team runs and workflow runs confirmed from an Issue stay `queued` in production |
| `ExecutionDispatcher`, `ExecutionObserver` | Hand queued runs to an external executor and observe their progress | Not wired: team runs stay `queued` in production; workflow IssueRuns bypass it and execute through their linked WorkflowRun |

Agent runs do not go through `ExecutionDispatcher`; Cloud's own scheduling carries them (see
[Agent IssueRuns](../agent-runs/issue-runs.en.md)). Runs started directly from a workflow are a different
aggregate, WorkflowRun; see [Workflows](../workflows/workflows.en.md).

For a linked workflow IssueRun, actual WorkflowRun status updates advance it in the same transaction:
updates affecting no rows do not project, `awaiting_input` leaves it unchanged, and contract conflicts
roll back the transaction. Output summaries are `system` `run.message` activities bounded to 20000
bytes of valid UTF-8, never workflow-authored comments. Direct workflow runs without a linked IssueRun
create none of these collaboration records.

**Development fixtures.** `collaboration.development_fixtures: true` (environment variable
`CLOUD_COLLABORATION_DEVELOPMENT_FIXTURES`) installs the in-process doubles from `internal/collab` on the
Store: Agent and team targets with fixed IDs, a deterministic context builder (at most the 10 latest
Expand Down
25 changes: 20 additions & 5 deletions docs/collaboration/interactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,33 @@ Agent 名册(`space_agents`,由已安装的 Agent 插件形成,见 [插件
工作流的执行边界只有一个:显式的**确认**。

```text
选中工作流 ──▶ 读取表单描述 ──▶ 填写(可选 AI 辅助)──▶ 确认 ──▶ IssueRun
选中工作流 ──▶ 读取表单描述 ──▶ 填写(可选 AI 辅助)──▶ 确认 ──▶ WorkflowRun + IssueRun
不执行 不执行 不执行、不建运行 唯一的执行边界
```

- **表单描述**(`GET /tenants/:tid/collaboration/forms/:formRef?issueId=…`)是面向 Issue 的渲染描述:`formRef`、标题、说明和
字段列表。它不是工作流的 schema,也不绑定任何 schema 技术。字段分三段:平台字段、工作流 Start 节点声明的变量、图的全局变量。
前端先读取当前文档,只为定位平台版本选择器及其默认版本;确定默认版本(最新已发布版本)后,初次打开就按该版本通过
`GET /tenants/:tid/workflows/:wfid/snapshots/:snapshotId/form?issueId=…` 读取表单,切换版本时按新版本重新读取。
因此显示的表单与 Confirm 校验的版本始终相同,活稿里未发布的字段不出现在默认视图。
这条同源承诺的前提是平台能指定版本,而选择器由活稿决定。活稿自己声明了保留键 `snapshotId`(或用启动字段移除它)时没有
可选的版本:`@` 表单按活稿渲染,Confirm 仍按活稿的版本规则执行最新已发布版本,显示的字段可能与执行的版本不同。
选择器存在、但用户切到的那个版本自己的图声明了 `snapshotId`(或它的 select 选项不全是已发布 id)时,该版本的快照表单里
没有平台字段,Confirm 仍按活稿判定这个键归平台,提交里的作者值会被当成版本游标:空值落到最新已发布版本,非 id 值被拒。
两种情况都是作者占用保留键时的边界,不是可依赖的配置:作者不应把 `snapshotId` 用作自己的变量名。是否在切换时禁止或提示
这类版本,由后续决策另行确定(circus#405)。
- **平台字段**只在带着 Issue 打开表单时注入:仓库地址和分支(默认取 Issue 关联 Project 的仓库与默认分支,必填)、提示词、要执行
的已发布版本 `snapshotId`、附加的上下文引用 `context_refs`。工作流自己声明了同名变量时,以工作流的声明为准。
- **AI 辅助**(`POST .../issues/:iid/collaboration/assist`)只返回字段级的建议补丁和建议的上下文引用:从不创建运行,也不写入
持久的上下文引用。应用建议是用户的动作。生产环境没有接入辅助提供方,返回 `503 assist_unavailable`。
持久的上下文引用。应用建议是用户的动作。它按与 Confirm 相同的版本描述判断提交值的形状,所以有已发布版本时只接受该版本声明的字段。
生产环境没有接入辅助提供方,返回 `503 assist_unavailable`。
- **确认**(`POST .../issues/:iid/interactions/:ixid/confirm`)是唯一把工作流交互变成执行意图的转换:
- 前端校验只是体验,Cloud 在确认时**重新**解析目标,并按**被确认的那个已发布版本**的表单校验提交的值;没有任何已发布版本的
工作流不能确认,因为不能执行未发布的草稿。
- 用户看到的表单来自当前文档;如果它与被确认版本的字段已经不一致,确认被拒绝并列出变化的字段
(`409 workflow_version_fields_changed`),而不是用用户没见过的 schema 校验他填的值。
- Cloud 只按所选发布版本自身的字段校验,不再与当前活稿比较。选择旧版本就是按那份快照执行:即使它的字段与活稿不同,
提交符合该版本表单即可通过;缺少该版本必填字段或提交它未声明的字段时,按该版本的 schema 拒绝。
- 提交的值作为交互的输入保存,被执行的版本冻结进运行输入;运行之后工作流再被编辑或发布,它仍然知道自己执行的是哪个版本。
- 同一事务里创建一个 WorkflowRun 和一个 IssueRun,IssueRun 的 `workflowInvocationRef` 指向该 WorkflowRun(与租户一起的复合外键,且唯一);直接运行、manual 工作流 IssueRun 与确认共用同一个创建函数。已关联的 IssueRun 不交给 `ExecutionDispatcher`。
- 用 `run_id IS NULL` 的比较并交换认领交互,重复确认返回 `409 interaction_already_confirmed`;对非表单交互确认返回
`409 interaction_not_confirmable`。

Expand All @@ -83,11 +94,15 @@ Issue 协作通过一组端口向外部能力请求"做一件事",而不定义
| `FormDescriptorProvider` | 按 `formRef` 给出表单描述 | 工作流文档投影成的表单 |
| `InputAssistProvider` | 表单的 AI 辅助 | 未接入(503) |
| `ContextBuilder` | 组装运行的上下文快照 | 未接入,退回到"任务、表单值、目标、原始上下文引用"的确定性快照 |
| `ExecutionDispatcher`、`ExecutionObserver` | 把排队的运行交给外部执行者,并观察其进度 | 未接入:团队运行和从 Issue 确认的工作流运行在生产中保持 `queued` |
| `ExecutionDispatcher`、`ExecutionObserver` | 把排队的运行交给外部执行者,并观察其进度 | 未接入:团队运行在生产中保持 `queued`;工作流 IssueRun 不经过这里,由关联的 WorkflowRun 执行 |

Agent 运行不走 `ExecutionDispatcher`,由 Cloud 自己的调度推进(见 [Agent IssueRun](../agent-runs/issue-runs.md))。直接从工作流
发起的运行是另一个聚合 WorkflowRun,见 [工作流](../workflows/workflows.md)。

关联工作流的 IssueRun 由 WorkflowRun 的实际状态更新在同一事务内推进:未更新到行时不投影,`awaiting_input` 不改变 IssueRun,
契约冲突使事务回滚。输出摘要写成 `system` 的 `run.message` 活动,最多 20000 字节且保持合法 UTF-8,不创建工作流作者评论。
没有关联 IssueRun 的直接工作流运行不产生这些协作记录。

**开发夹具。** `collaboration.development_fixtures: true`(环境变量 `CLOUD_COLLABORATION_DEVELOPMENT_FIXTURES`)在 Store 上装入
`internal/collab` 的进程内替身:固定 ID 的 Agent 和团队目标、确定性的上下文构建器(最近至多 10 条评论,不用 AI)、给出固定输出的
派发器和 AI 辅助。工作流目标仍然来自数据库。这个开关只用于开发和演示,生产默认关闭;它与认证完全独立:开启 GitHub 登录不会打开
Expand Down
17 changes: 12 additions & 5 deletions docs/workflows/workflows.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ Workflows have three start entries, all sharing one admission gate:
| Entry | Produces |
|---|---|
| `POST .../workflows/:wfid/runs` | A WorkflowRun (`workflow_runs`) |
| `POST .../issues/:iid/runs` with `executorType = workflow` | A workflow IssueRun |
| Confirming an @workflow form on an Issue | A workflow IssueRun (see [Issue collaboration](../collaboration/interactions.en.md#form-mode-workflow)) |
| `POST .../issues/:iid/runs` with `executorType = workflow` | A WorkflowRun and the workflow IssueRun linked to it |
| Confirming an @workflow form on an Issue | A WorkflowRun and the workflow IssueRun linked to it (see [Issue collaboration](../collaboration/interactions.en.md#form-mode-workflow)) |

**Admission** is decided once, in the caller's transaction:

Expand All @@ -77,8 +77,10 @@ Workflows have three start entries, all sharing one admission gate:
If any check fails, no run, claimed interaction or activity is left behind. The executed version is frozen onto the
run, so the run still knows which version it executed after the workflow is edited or published again.

**A run's identity is frozen at creation.** A direct run must name its `projectId`: a missing one is `409
workflow_run_project_required`, an unusable, archived or foreign-space Project is refused as `404`, and one being
**A run's identity is frozen at creation.** All three entries share one creation function: a direct
start takes the Project from the request body's `projectId`, while Confirm/manual take it from the
Issue's `project_ref`. A missing Project is `409 workflow_run_project_required` for every entry;
an unusable, archived or foreign-space Project is refused as `404`, and one being
deleted is `409 resource_unavailable`. The run writes the initiating user (`created_by`) and that Project
(`project_id`) into its row and never changes them — a workflow document belongs to a tenant, not to a Project, so
there is nothing to default to, and the run's Workspace, its clone and its whole checkout land in the Project that
Expand Down Expand Up @@ -165,7 +167,12 @@ seconds):

Development fixtures (`collaboration.development_fixtures`) install a deterministic run simulator that fills in the
execution trace at run creation and says in its output that nothing really ran; production never installs it.
Workflow IssueRuns currently have no executor in production and stay `queued` (see
A workflow IssueRun is linked through `workflow_invocation_ref` to the WorkflowRun created in the same transaction,
and the scheduling loop above advances that run. After a WorkflowRun status update affects a row, its state,
timestamps and errors are projected onto the linked IssueRun in that same transaction. An update affecting no rows
does not project; contract conflicts roll back the transaction. `awaiting_input` leaves the IssueRun unchanged,
and direct runs without a linked IssueRun create no collaboration records. Output summaries are `system`
`run.message` activities, bounded to 20000 bytes of valid UTF-8, never workflow-authored comments (see
[Issue collaboration](../collaboration/interactions.en.md#ports-issues-own-only-the-consuming-side-contract)).

## Plugin asset import
Expand Down
Loading
Loading