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

159 lines
9.7 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 已接入。用于查看流程、用户定义步骤与信号已经保存的状态变化;原事件不会被后续恢复覆盖。运行方法见[并行作业与回执示例](../examples/workflow.md),验证记录见[时间线报告](../reports/phase-5-events.md)。
## 1. 路由、权限与范围
| 方法与路径 | 权限 | Go SDK |
| --- | --- | --- |
| `GET /v1/workflows/{id}/events` | workflows:audit:read | ListWorkflowEvents(ctx, id, page) |
| `GET /v1/workflows/{id}/events/{event}` | workflows:audit:read | GetWorkflowEvent(ctx, id, event) |
成功为200,使用 `Authorization: Bearer <token>`,无需请求体或幂等键。id和event均为32位小写十六进制。两条路由都要求显式审计权限,普通workflows:read不包含此权限;开发初始化用 `--workflow-audit-access`。每次读取都核验当前命名空间及实例固定版本引用的全部队列,包括尚未执行的分支。
数据库派生的workflow_id只用于缩小查询范围,不能授予权限。服务端继续核对真实实例、步骤、信号及事件引用的历史激活归属;内部`$root`步骤不出现在公开时间线。详情必须同时指定父实例和事件ID,不能用全局事件ID越权。
本接口提供逻辑状态事件。任务执行次数、原输入结果、POLL业务轮次、等待预算和人工决定通过下列接口关联查看:
| 要查看的内容 | 接口 |
| --- | --- |
| 当前步骤、任务引用、等待原因和剩余预算 | [实例与步骤详情](workflows.md) |
| 指定历史激活的输入输出、等待、POLL轮次 | [流程历史](workflow-history.md) |
| Pulse尝试与迟到/矛盾证据 | [执行历史](execution-history.md) |
| 管理操作人、原因、证据及前后摘要 | [流程操作审计](workflows.md#6-操作审计与错误处理)、[信号审计](workflow-history.md) |
| 任务的固定版本、步骤和历史激活来源 | [任务来源](task-source.md#5-流程固定来源) |
事件元数据只保留当时实际写入的引用,例如activation_id或audit_id;旧事件没有的字段不会用当前资源补造。操作审计与Pulse尝试不复制进本接口,避免将不同记录误认为同一种状态转换。
## 2. 摘要分页
只接受可选limit和cursor。limit为1–100,省略默认20;SDK的0表示省略。cursor最多1024字节,需进行URL编码。未知、重复、非法参数返回400;详情不接受查询参数。
```sh
curl --get "$ORBIT_URL/v1/workflows/$WORKFLOW_ID/events" \
--header "Authorization: Bearer $ORBIT_TOKEN" \
--data-urlencode 'limit=20'
```
响应示例:
```json
{
"request_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": {
"workflow_id": "11111111111111111111111111111111",
"revision": "12",
"items": [{
"id": "22222222222222222222222222222222",
"workflow_id": "11111111111111111111111111111111",
"aggregate_kind": "workflow_signal",
"aggregate_id": "33333333333333333333333333333333",
"aggregate_revision": "2",
"type": "workflow.signal.changed",
"state_scope": "SIGNAL",
"state": "CONSUMED",
"occurred_at": "2026-09-18T08:00:00.123456Z"
}]
}
}
```
| 字段 | 含义 |
| --- | --- |
| workflow_id | 本页授权的实例 |
| revision | 读取本页时的实例版本,uint64字符串;不是事件消费水位 |
| items | 有界摘要;空列表为[],不带payload或业务输入结果 |
| next_cursor | 还有更旧记录时返回;没有下一页则省略 |
| items[].id | 原不可变事件ID |
| aggregate_kind / aggregate_id | 来源类型及身份:workflow、workflow_step、workflow_signal |
| aggregate_revision | 来源资源当时的版本,uint64字符串,各资源独立计数 |
| type | 当时保存的事件类型 |
| state_scope / state | WORKFLOW、STEP或SIGNAL,以及该对象当时保存的状态 |
| step_key | 仅步骤事件返回,取自固定定义,不返回内部根步骤 |
| occurred_at | 数据库记录事件的UTC微秒时间,RFC3339;不是外部业务发生时间 |
按occurred_at、id双键倒序,相同微秒由ID稳定定位。游标绑定命名空间、实例和事件查询种类,不能用于其他实例、操作审计或激活列表。跨页不承诺固定快照,新提交或迟到提交的事件可能落在已经翻过的位置;重新从第一页读取可查看更新。
时间戳及ID均不能表达严格因果或提交顺序。例如同一个启动事务中,首步状态可能先于workflow.started写入;跨资源aggregate_revision不能比较大小。定时退避可能改变资源版本而不改变状态,因此状态事件的版本也可能不连续。需要可靠处理Pulse结果时使用[持久订阅](subscriptions.md),不要将此审计分页当作消息消费协议。
## 3. 类型与原事件详情
| type | aggregate_kind | state_scope | 记录内容 |
| --- | --- | --- | --- |
| workflow.started | workflow | WORKFLOW | 首次启动、固定定义及版本 |
| workflow.advanced | workflow | WORKFLOW | 持久推进时保存的实例状态 |
| workflow.controlled | workflow | WORKFLOW | 暂停、继续、取消的控制结果 |
| workflow.recovered | workflow | WORKFLOW | 人工恢复决定及当时关联 |
| workflow.blocked | workflow | WORKFLOW | 进入核查等阻塞状态 |
| workflow.stopped | workflow | WORKFLOW | 停止意图结清后的终态 |
| workflow.step.changed | workflow_step | STEP | 步骤当时状态和已记录的激活/原因 |
| workflow.signal.accepted | workflow | SIGNAL | 保存信号受理记录,state可为PENDING、REJECTED或EXPIRED |
| workflow.signal.changed | workflow_signal | SIGNAL | 已有PENDING信号被消费、拒绝或过期 |
`workflow.signal.accepted`沿用历史格式,aggregate_id和aggregate_revision属于实例;state_scope为SIGNAL,信号ID在payload.signal_id中。受理成功不等于信号被消费,受理时就被拒绝的记录不会再制造一个虚假的PENDING转换。
详情data在摘要字段外增加payload,为原事件元数据对象,最大64KiB。例如上述信号消费事件的payload:
```json
{
"workflow_id": "11111111111111111111111111111111",
"signal_id": "33333333333333333333333333333333",
"from": "PENDING",
"state": "CONSUMED",
"disposition": "WAIT_MATCHED",
"consumed_wait_id": "44444444444444444444444444444444"
}
```
```sh
curl "$ORBIT_URL/v1/workflows/$WORKFLOW_ID/events/$EVENT_ID" \
--header "Authorization: Bearer $ORBIT_TOKEN"
```
信号消费、过期和拒绝,与对应事件在同一数据库事务提交。批量拒绝最多处理单实例既有上限1024条,每32条批量追加事件;任一批失败会回滚整笔转换,重复处理不重复制造事件。该原子性覆盖平台记录,不保证外部系统业务副作用只发生一次。
## 4. SDK调用
类型见[workflow_events.go](../../sdk/go/workflow_events.go)。返回revision和aggregate_revision自动解析为uint64;Payload为json.RawMessage,保留精确数字,进一步解析未知元数据时用json.Decoder.UseNumber或明确整数类型。
```go
cursor := ""
for {
page, err := client.ListWorkflowEvents(ctx, workflowID,
orbit.HistoryPageRequest{Limit: 20, Cursor: cursor})
if err != nil { return err }
for _, event := range page.Items {
fmt.Printf("%s %s %s %s\n", event.OccurredAt, event.Type,
event.StateScope, event.State)
// 仅在需要展开某条事件时读取原元数据。
if event.Type == "workflow.signal.changed" {
detail, err := client.GetWorkflowEvent(ctx, workflowID, event.ID)
if err != nil { return err }
fmt.Printf("%s\n", detail.Payload)
}
}
if page.NextCursor == "" { break }
cursor = page.NextCursor
}
```
完整[workflow示例](../examples/workflow.md)运行真实Worker,保存外部回执后按每页5条读取时间线,核对CONSUMED事件的原PENDING状态及赢家等待ID。
## 5. 错误、升级与验证边界
| HTTP / code | 处理 |
| --- | --- |
| 400 / INVALID_ARGUMENT | 修正非法ID、limit、cursor或参数;不要删除父资源或切换查询种类重试 |
| 401 / UNAUTHENTICATED | 检查有效凭据 |
| 403 / PERMISSION_DENIED | 显式授权workflows:audit:read |
| 404 / NOT_FOUND | 实例/事件不存在、父子错配、范围不可访问;不按全局ID降级查找 |
| 503 / UNAVAILABLE | 数据库故障、已选事件归属损坏、未知历史类型或超大元数据;保留现场核查,不能把空页当作成功 |
只读请求可在原实例与游标下有限重试;数据改变后结果可能不同。取消HTTP只取消本次读取,不控制流程。SDK拒绝父资源不一致、无效摘要、排序不符和超限详情,不自动补造历史。
迁移000025新增只读派生列及`(namespace_id,workflow_id,occurred_at,id)`索引,无外键和级联。已有事件原字段不变;无合法流程范围的历史行派生为NULL,不进入流程时间线。新读取仍核验实际归属,但本接口不是全库损坏扫描,无法自动找出被错误范围隐藏的历史行。
旧版本没有保存的信号转换不会回填。部署时先停止旧写入节点,完成迁移再启动新节点,否则混合写入期间可能继续缺少转换事件。STORED生成列和索引需要读取历史数据并可能重建表,生产升级须预演锁等待、时长及磁盘空间;不承诺无停机DDL。旧不可变迁移保持原校验和。
真实MySQL验证包括升级原文保留、零外键、相同时间分页、授权、跨父资源、消费/拒绝/过期、分批写入失败回滚、重复处理和历史激活恢复;HTTP/SDK及完整例子已接入标准回归。大库索引开销、生产故障域、主从切换与全量运维仍属于P6/P7验收。