Files
ai-workflow-skill/docs/tests/inbox/workflows/README.md
T

277 lines
9.8 KiB
Markdown

# Inbox Workflow Test Plan
## Scope
This document tracks cross-command scenarios where the main value is the interaction between multiple `inbox` subcommands.
All examples assume:
- isolated temp database
- `inbox --db TMPDIR/coord.db --json init` already executed
- assertions follow the shared rules in [../_shared/README.md](../_shared/README.md)
## case: thread-lifecycle-happy-path
### 用例意义
验证 `send -> fetch -> claim -> update(in_progress) -> update(blocked) -> reply -> done -> show` 的主干链路可用,且线程与消息历史一致。
### 前置条件
- 空数据库已完成 `init`
- 发送方为 `leader`
- 执行方为 `worker-a`
### 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-a --subject "Implement feature X" --summary "Add retry policy" --body "Implement retry handling for the HTTP client." --run run_blog_001 --task T1
inbox --db TMPDIR/coord.db --json fetch --agent worker-a --status pending
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID --lease-seconds 300
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status in_progress --summary "Implementation started" --body "Scanning current HTTP client usage."
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status blocked --summary "Need timeout decision" --payload-json '{"question":"Should retries apply to read timeouts?"}'
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-a --thread THREAD_ID --summary "Retry read timeouts" --body "Yes, include read timeouts in the retry policy."
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Retry policy implemented" --body "The HTTP client now retries the selected transient failures."
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `send` 返回新建线程,线程状态为 `pending`
- `fetch` 返回唯一匹配线程
- `claim` 后线程状态为 `claimed`
- 第一次 `update` 后线程状态为 `in_progress`
- 第二次 `update` 后线程状态为 `blocked`
- `reply` 返回一条 `kind=answer` 的消息
- `done` 后线程状态为 `done`
- `show` 返回线程状态 `done`,并包含完整消息历史
### 断言结论
- 全链路所有命令退出码为 `0`
- `show.data.thread.status == "done"`
- `show.data.messages` 长度为 `6`
- 历史中的状态推进顺序与执行顺序一致,不出现丢消息或状态回退
## case: blocked-question-reply-resume-to-done
### 用例意义
验证被阻塞线程在收到答复后可以继续推进,并最终进入完成态。
### 前置条件
- 已存在由 `leader` 发给 `worker-c` 的线程
- `worker-c` 已经成功 `claim`
### 输入
```bash
inbox --db TMPDIR/coord.db --json update --agent worker-c --thread THREAD_ID --status blocked --summary "Need policy decision" --body "Should guest users be redirected to login or shown a 403 page?"
inbox --db TMPDIR/coord.db --agent worker-c --json wait-reply --thread THREAD_ID --after-message BLOCKED_MESSAGE_ID --timeout-seconds 2
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-c --thread THREAD_ID --summary "Redirect to login" --body "Redirect guests to login for the MVP."
inbox --db TMPDIR/coord.db --json done --agent worker-c --thread THREAD_ID --summary "Policy applied" --body "The flow now redirects guests to login."
```
### 预期输出
- `update` 将线程推进到 `blocked`
- `wait-reply` 在答复出现后唤醒
- 唤醒结果包含答复消息
- `done` 成功将线程推进到 `done`
### 断言结论
- `wait-reply.data.woke == true`
- `wait-reply.data.message.kind == "answer"`
- 最终 `done.data.thread.status == "done"`
- 该用例强调“阻塞后可恢复”,不是单纯验证 reply 本身
## case: fail-lifecycle-from-claim-to-terminal
### 用例意义
验证线程在被领取后可以直接进入失败终态,并且 `show` 对终态读取一致。
### 前置条件
- 空数据库已完成 `init`
- `leader` 已向 `worker-b` 发送任务
- `worker-b``claim` 该线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json fail --agent worker-b --thread THREAD_ID --summary "Migration failed" --body "The migration cannot proceed because the prior schema is inconsistent."
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `fail` 返回线程状态 `failed`
- `show` 返回相同终态
### 断言结论
- `fail.data.thread.status == "failed"`
- `show.data.thread.status == "failed"`
- 失败消息保留在线程历史中,可被后续排障读取
## case: cancel-lifecycle-after-worker-claim
### 用例意义
验证线程在执行者已领取后,发起方仍可以取消任务,并进入 `cancelled` 终态。
### 前置条件
- `leader` 已向 `worker-c` 发送任务
- `worker-c` 已成功 `claim`
### 输入
```bash
inbox --db TMPDIR/coord.db --json cancel --agent leader --thread THREAD_ID --reason "Task superseded by a larger refactor"
```
### 预期输出
- `cancel` 成功
- 返回线程状态 `cancelled`
- 返回的消息记录取消原因
### 断言结论
- `cancel.data.thread.status == "cancelled"`
- 取消属于终态转换,不要求执行者先主动释放 lease
- 原因字段可被后续 `show` 或审计场景消费
## case: watch-wakes-then-fetch-sees-new-thread
### 用例意义
验证 `watch` 的等待语义与 `fetch --unread` 的可见性一致,确保新线程到达时执行者既会被唤醒,也能随后拉到未读任务。
### 前置条件
- `worker-d` 尚无匹配 `pending` 线程
- `watch` 先于 `send` 启动
### 输入
```bash
inbox --db TMPDIR/coord.db --json watch --agent worker-d --status pending --timeout-seconds 2
inbox --db TMPDIR/coord.db --json send --from leader --to worker-d --subject "Build admin editor" --summary "Create the first editor screen" --body-file TMPDIR/task.md --artifact TMPDIR/task.md --artifact-kind brief --artifact-metadata-json '{"label":"task-brief"}' --run run_blog_004 --task T4
inbox --db TMPDIR/coord.db --json fetch --agent worker-d --status pending --unread
```
### 预期输出
- `watch` 因新线程到达而唤醒
- 唤醒结果中的 `thread_id``send` 返回值一致
- 随后 `fetch --unread` 仍能看到该 `pending` 线程
### 断言结论
- `watch.data.woke == true`
- `watch.data.thread.thread_id == send.data.thread.thread_id`
- `fetch.data.threads` 长度为 `1`
- `watch` 唤醒不应提前消费掉线程的未读可见性
## case: artifact-visible-through-send-and-show
### 用例意义
验证 `send` 写入的 body-file 与 artifact 信息能被后续 `show` 完整读回。
### 前置条件
- `TMPDIR/task.md` 已存在,内容为测试任务正文
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-d --subject "Build admin editor" --summary "Create the first editor screen" --body-file TMPDIR/task.md --artifact TMPDIR/task.md --artifact-kind brief --artifact-metadata-json '{"label":"task-brief"}' --run run_blog_004 --task T4
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `send` 成功创建线程并附带一条 artifact
- `show` 的首条消息包含从文件读取的正文与 artifact 列表
### 断言结论
- 首条消息 `body` 等于 `TMPDIR/task.md` 的文件内容
- 首条消息 `artifacts` 长度为 `1`
- 首个 artifact 的 `path` 等于 `TMPDIR/task.md`
- 首个 artifact 的 `kind` 等于 `brief`
## case: unread-clears-after-mark-read-and-reappears-on-new-message
### 用例意义
验证 read cursor 的最关键用户感知行为:未读任务可被显式清空,并会在同线程新消息到达后重新出现。
### 前置条件
- `leader` 已向 `worker-e` 发送一个 `pending` 线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json fetch --agent worker-e --status pending --unread
inbox --db TMPDIR/coord.db --agent worker-e --json show --thread THREAD_ID --mark-read
inbox --db TMPDIR/coord.db --json fetch --agent worker-e --status pending --unread
inbox --db TMPDIR/coord.db --json send --from leader --to worker-e --thread THREAD_ID --summary "Use sentence case" --body "Keep the nav labels in sentence case."
inbox --db TMPDIR/coord.db --json fetch --agent worker-e --status pending --unread
```
### 预期输出
- 第一次 `fetch --unread` 返回该线程
- `show --mark-read` 成功推进 `worker-e` 的 read cursor
- 第二次 `fetch --unread` 无匹配结果
- 新消息追加后,第三次 `fetch --unread` 再次返回该线程
### 断言结论
- 第一次 `fetch` 返回 1 条线程
- 第二次 `fetch` 退出码为 `10`,错误码为 `no_matching_work`
- 追加消息后第三次 `fetch` 再次返回 1 条线程
- 未读状态是按 agent 视角计算,而不是线程级布尔值
## case: wait-reply-clears-blocked-unread-for-agent
### 用例意义
验证等待答复的消费者在收到答复后,其阻塞线程未读状态会被消费,避免“已经处理过回复但列表仍显示未读”的错觉。
### 前置条件
- `worker-c` 已拥有一个 `blocked` 线程
- 该线程阻塞消息对应的 `message_id` 已知
- `worker-c` 使用 `wait-reply` 等待答复
### 输入
```bash
inbox --db TMPDIR/coord.db --agent worker-c --json wait-reply --thread THREAD_ID --after-message BLOCKED_MESSAGE_ID --timeout-seconds 2
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-c --thread THREAD_ID --summary "Redirect to login" --body "Redirect guests to login for the MVP."
inbox --db TMPDIR/coord.db --agent worker-c --json fetch --status blocked --unread
```
### 预期输出
- `wait-reply` 在答复后唤醒
- 唤醒结果携带 `answer` 消息
- 随后的 `fetch --status blocked --unread` 不再返回该线程
### 断言结论
- `wait-reply.data.woke == true`
- `wait-reply.data.message.kind == "answer"`
- 后续 `fetch` 退出码为 `10`
- 对等待中的 agent 来说,答复消费与未读清理是同一条用户契约链路