Files
orbit/docs/api/workflow-history.md

165 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 流程激活、查询轮次、信号与信号审计
状态:HTTP与独立Go SDK已接入;本页查询明确指定的历史激活,不跟随逻辑步骤的当前激活。[实例筛选分页](workflow-queries.md)和[状态事件时间线](workflow-events.md)已接入,更多独立恢复场景仍按[开发计划](../08-development-plan.md)交付。测试与范围见[本批报告](../reports/phase-5-history.md)。
## 1. 路由与调用规则
所有接口为GET,成功200,使用`Authorization: Bearer <token>`,不需要请求体或幂等键。`{id}`是流程实例ID,`{activation}`是激活ID,`{signal}`是信号ID,`{operation}`是审计记录ID;均为32位小写十六进制。`{key}`是固定定义中的步骤键,1–64个ASCII字符,首位字母或数字,之后允许字母、数字、`_ . -`,禁止内部`$root`。操作审计的operation_id不是查询路径中的审计ID。
| 路径 | 权限 | SDK方法 |
| --- | --- | --- |
| `/v1/workflows/{id}/steps/{key}/activations` | workflows:read | ListWorkflowActivations(ctx, id, key, page) |
| `/v1/workflows/{id}/steps/{key}/activations/{activation}` | workflows:read | GetWorkflowActivation(ctx, id, key, activation) |
| `/v1/workflows/{id}/steps/{key}/activations/{activation}/rounds` | workflows:read | ListWorkflowPollRounds(ctx, id, key, activation, page) |
| `/v1/workflows/{id}/signals` | workflows:read | ListWorkflowSignals(ctx, id, page) |
| `/v1/workflows/{id}/signals/{signal}/operations` | workflows:audit:read | ListWorkflowSignalOperations(ctx, id, signal, page) |
| `/v1/workflows/{id}/signals/{signal}/operations/{operation}` | workflows:audit:read | GetWorkflowSignalOperation(ctx, id, signal, operation) |
服务端先校验命名空间和固定流程引用的全部队列,再读取子资源。普通流程读取权限不包含审计权限;bootstrap用`--workflow-audit-access`显式签发审计权限。知道激活、任务、信号或审计ID不能绕过父资源权限。
四个列表只接受可选`limit`、`cursor`。limit为十进制整数1–100,省略默认20;SDK的`HistoryPageRequest.Limit=0`表示省略。cursor最多1024字节,按URL查询参数编码,必须来自相同查询种类、实例以及对应步骤/激活/信号。未知、重复或非法参数返回400;详情不接受查询参数。
每页最多读取limit+1条判断后续,空列表返回`items: []`,没有下一页时省略next_cursor。游标不是授权凭据;改权限后继续分页仍重新授权。翻页之间不是固定数据库快照,状态可能变化、新记录可能产生;要看最新记录需重新从第一页开始,不能把游标用于执行或确认操作。
## 2. 激活列表和单次详情
列表按sequence倒序,不聚合input、output、查询预算或等待正文。返回类型是[WorkflowActivationPage](../../sdk/go/workflow_history_types.go):
| 字段 | 类型与含义 |
| --- | --- |
| instance_id、step_id | 字符串,已验证父实例与逻辑步骤 |
| revision | 十进制字符串,本页读取时的实例版本;SDK为uint64 |
| current_activation_id | 可选字符串,本页读取时的当前激活;尚未激活时省略 |
| items | 激活摘要数组,未激活步骤为[] |
| next_cursor | 可选字符串,后续位置 |
每项含id、step_id、sequence(正uint32)、state、revision(uint64字符串)、created_at、updated_at;task_id仅用于TASK直接任务,completed_at仅已保存结束时间时返回。时间均为UTC RFC3339。state反映这条激活当前保存的状态,不代表整个实例状态;核查可能改变记录状态,列表不是每次状态变化的事件流水。
下面是恢复后的第二页,当前激活已经是第2代,本页仍读取第1代:
```json
{
"request_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": {
"instance_id": "11111111111111111111111111111111",
"revision": "12",
"step_id": "22222222222222222222222222222222",
"current_activation_id": "44444444444444444444444444444444",
"items": [{
"id": "33333333333333333333333333333333",
"step_id": "22222222222222222222222222222222",
"sequence": 1,
"state": "FAILED",
"revision": "3",
"task_id": "55555555555555555555555555555555",
"created_at": "2026-09-18T08:00:00Z",
"updated_at": "2026-09-18T08:00:01Z",
"completed_at": "2026-09-18T08:00:01Z"
}]
}
}
```
详情的data为`{instance_id, revision, step, activation, input, output?, poll?, wait?}`:
- `step`是[逻辑步骤当前视图](workflows.md#3-步骤及当前激活),`activation`是明确请求的历史激活摘要,两者的激活ID和代次允许不同。
- `input/output`只属于指定激活。缺失output与显式null不同,精确JSON数字由SDK的json.RawMessage保留;不会展开其他激活大正文。
- POLL的`poll`返回这一激活的first_round、first_round_attempts_used、used_rounds、冻结上限、原截止和最后任务。字段与[当前查询详情](workflows.md#3-步骤及当前激活)一致,范围改为指定激活。
- SIGNAL、TIMER和有最终信号的POLL返回该激活的wait,包含原等待ID、原截止、状态和已有赢家。恢复后旧等待仍可读,新激活不能延长原截止或替换旧winner。
- TASK尝试、未知结果和迟到证据通过task_id继续查询[执行历史](execution-history.md)。人工决定的前后摘要通过[流程操作审计](workflows.md#6-操作审计与错误处理)读取。
## 3. POLL轮次列表
data为`{instance_id, revision, activation_id, items, next_cursor?}`,items按业务轮round倒序。每项为`{id, activation_id, round, task_id, created_at}`;round是正uint32,其他身份为字符串。
必须明确指定一个POLL激活;TASK或其他节点调用返回400。人工恢复同一业务轮会新建激活和任务,因此“第1代的业务第1轮”和“第2代的业务第1轮”可以同时存在。先遍历激活,再遍历每个激活的轮次,不能把激活序号、业务轮次和Pulse尝试epoch混为一谈。
```json
{
"request_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": {
"instance_id": "11111111111111111111111111111111",
"revision": "20",
"activation_id": "44444444444444444444444444444444",
"items": [{
"id": "66666666666666666666666666666666",
"activation_id": "44444444444444444444444444444444",
"round": 1,
"task_id": "77777777777777777777777777777777",
"created_at": "2026-09-18T08:00:02Z"
}]
}
}
```
列表显式验证每轮任务属于该命名空间、激活、固定队列和门控。关联损坏不返回部分成功,也不利用外键推断归属。GetTaskSource可以反向核对[固定来源](task-source.md#5-流程固定来源)。
## 4. 信号与信号资源审计
信号列表data为`{workflow_id, revision, items, next_cursor?}`,按accepted_at和id倒序。每项含id、workflow_id、revision、name、payload_version、target_step_key、target_activation_sequence、state、accepted_at、expires_at,有处置原因时增加disposition。列表不携带payload、correlation_key、actor_id或evidence_ref;完整原信号使用[GetWorkflowSignal](workflows.md)读取。
早期未核验历史可能payload_version为空、target_activation_sequence为0,列表保留这些事实,不补造可执行身份。CONSUMED表示该信号已消费,单条详情的consumed_wait_id与指定历史激活的wait.winner_signal_id可以交叉核对。
信号操作列表data为`{items, next_cursor?}`,按created_at和id倒序,每项含id、workflow_id、signal_id、operation_id、actor_id、action、created_at。当前受理动作action为`workflow.signal`。操作详情的data直接是该摘要,再增加reason、可选evidence_ref、before和after:
```json
{
"request_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": {
"id": "88888888888888888888888888888888",
"workflow_id": "11111111111111111111111111111111",
"signal_id": "99999999999999999999999999999999",
"operation_id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"actor_id": "cccccccccccccccccccccccccccccccc",
"action": "workflow.signal",
"created_at": "2026-09-18T08:00:02Z",
"reason": "verified external completion",
"evidence_ref": "proof:event-1",
"before": null,
"after": {"signal_id": "99999999999999999999999999999999", "state": "PENDING"}
}
}
```
上例after省略其他受理身份字段,完整内容以原操作保存的JSON为准。after是原受理审计,之后信号已消费也不能把其中PENDING改成CONSUMED。此接口只查`workflow_signal`资源,流程推进/控制/恢复操作在流程审计中。普通信号消费状态通过Get/List查看,不能将本接口解释成所有内部状态事件的统一时间线。
## 5. SDK调用场景
以下代码接在已配置client与ctx、已知workflowID及stepKey的调用方中;page使用SDK类型,与服务端模型无依赖:
```go
cursor := ""
for {
page, err := client.ListWorkflowActivations(ctx, workflowID, stepKey,
orbit.HistoryPageRequest{Limit: 20, Cursor: cursor})
if err != nil { return err }
for _, activation := range page.Items {
detail, err := client.GetWorkflowActivation(ctx, workflowID, stepKey, activation.ID)
if err != nil { return err }
fmt.Printf("activation=%s sequence=%d state=%s\n",
detail.Activation.ID, detail.Activation.Sequence, detail.Activation.State)
// 按需读取单次结果;禁止拿当前step的代次冒充历史激活代次。
}
if page.NextCursor == "" { break }
cursor = page.NextCursor
}
```
POLL继续用ListWorkflowPollRounds(ctx, workflowID, stepKey, activation.ID, page)分页获取该次激活的任务,再按任务查询尝试和证据。信号场景用ListWorkflowSignals定位旧代次和当前代次的信号;具备审计权限后用ListWorkflowSignalOperations和GetWorkflowSignalOperation读取原因与原受理证据。
[可运行并行作业示例](../examples/workflow.md)会核对已完成等待的历史赢家与CONSUMED信号。[SDK真实链路测试](../../sdk/go/workflow_instances_integration_test.go)另外覆盖TASK恢复、SIGNAL换代、POLL同轮恢复及下一业务轮分页;入口为`make integration`。调用方法见[SDK源码](../../sdk/go/workflow_history.go),各字段中文注释见[独立类型](../../sdk/go/workflow_history_types.go)。
## 6. 错误、部署与兼容
| HTTP / 错误码 | 含义与处理 |
| --- | --- |
| 400 INVALID_ARGUMENT | 身份、步骤键、分页参数或游标范围错误;或非POLL读取轮次。修正查询,不能丢掉筛选继续 |
| 401 UNAUTHENTICATED | 凭据无效或过期,按[认证协议](authentication.md)处理 |
| 403 PERMISSION_DENIED | 缺少读取或显式审计权限 |
| 404 NOT_FOUND | 父/子资源不属于该作用域、不存在或队列不可访问;不跨资源查找 |
| 503 UNAVAILABLE | 关联损坏或数据库暂不可用;保留资源身份并检查服务记录,不能将空列表当作成功 |
所有查询只读,不重算截止、重置预算、消耗轮次或触发恢复。SDK拒绝非法路径/分页及响应父资源不匹配;不自动创建新身份。读操作网络失败可以按原资源重读,状态可能已经前进。
服务端需先应用`000023_workflow_signal_history`,只增加(namespace_id,workflow_id,accepted_at,id)索引;原数据、迁移历史、模型字段和零外键要求保持。维护窗口和DDL锁需按目标数据库演练。当前SDK仍为开发版,使用与服务端匹配的开发提交;历史/事件功能已进入P4/P5阶段验收,生产容量、跨版本和高可用验收仍未完成。