docs: split inbox test plans into case files

This commit is contained in:
2026-03-19 11:29:17 +08:00
parent 9beb7e93eb
commit 5f59350577
70 changed files with 1820 additions and 1606 deletions
+19 -12
View File
@@ -20,7 +20,7 @@ As of now:
- `inbox` is implemented end-to-end, including send/fetch/claim/renew/update/reply/done/fail/cancel/list/show/watch/wait-reply - `inbox` is implemented end-to-end, including send/fetch/claim/renew/update/reply/done/fail/cancel/list/show/watch/wait-reply
- `inbox` supports blocking waits, lease renewal, unread fetches backed by per-agent read cursors, `--body-file`, artifact attachments, and structured JSON errors with stable exit codes - `inbox` supports blocking waits, lease renewal, unread fetches backed by per-agent read cursors, `--body-file`, artifact attachments, and structured JSON errors with stable exit codes
- integration tests cover the main inbox lifecycle, wait/watch flows, artifact persistence, and JSON error contracts - integration tests cover the main inbox lifecycle, wait/watch flows, artifact persistence, and JSON error contracts
- a human-readable inbox command test-plan set has not been authored yet - a human-readable inbox command test-plan set has been authored under `docs/tests/inbox/`
- `orch` currently exists as a command skeleton only - `orch` currently exists as a command skeleton only
- no scheduler workflows have been implemented yet - no scheduler workflows have been implemented yet
@@ -270,13 +270,10 @@ Definition of done:
If a new agent is taking over now, the next concrete step should be: If a new agent is taking over now, the next concrete step should be:
1. create the inbox test documentation tree under `docs/tests/inbox/` 1. start `Milestone 4: Orch Core Scheduling`
2. write the shared testing conventions in `docs/tests/inbox/README.md` 2. keep the authored inbox test-plan set in `docs/tests/inbox/` synchronized if CLI behavior changes during `orch` work
3. add `_shared/README.md` for common fixtures and assertion rules
4. add command-level `README.md` files for the implemented inbox commands
5. add `workflows/README.md` for cross-command cases such as unread, wait, and reply flows
This is the smallest meaningful documentation slice because the inbox implementation is already present and stable enough to document in detail before `orch` work begins. The inbox implementation and its human-readable test-plan set are already in place, so the next meaningful project step is to build scheduler behavior on top of that stable base.
## Recommended Driver Choices ## Recommended Driver Choices
@@ -302,6 +299,12 @@ Add these tests before the codebase grows too much:
## Inbox Test Documentation Roadmap ## Inbox Test Documentation Roadmap
Status:
- completed for the current inbox CLI surface
- command-level and workflow Markdown documents exist under `docs/tests/inbox/`
- future updates should revise this section only when new inbox commands or materially new CLI-visible behavior are added
Goal: Goal:
- make inbox behavior easy for a new agent to understand and convert into automated tests without re-reading all code paths - make inbox behavior easy for a new agent to understand and convert into automated tests without re-reading all code paths
@@ -312,6 +315,7 @@ Directory layout:
- `docs/tests/inbox/_shared/README.md` - `docs/tests/inbox/_shared/README.md`
- `docs/tests/inbox/workflows/README.md` - `docs/tests/inbox/workflows/README.md`
- `docs/tests/inbox/<command>/README.md` - `docs/tests/inbox/<command>/README.md`
- `docs/tests/inbox/<command>/<case-slug>.md`
Initial command folders: Initial command folders:
@@ -332,9 +336,11 @@ Initial command folders:
Documentation rules: Documentation rules:
- organize by folder and `README.md`, not one file per test case - organize by folder with a `README.md` entrypoint
- command folders use `README.md` as an index only
- each command case lives in its own Markdown file named after the case slug
- do not use numeric test case IDs - do not use numeric test case IDs
- identify cases by file path plus a stable case title or `slug` - identify command cases by concrete file path
- keep one command per directory, plus `workflows/` for cross-command behavior - keep one command per directory, plus `workflows/` for cross-command behavior
- use `_shared/` for common fixtures, database conventions, exit-code rules, and shared JSON assertions - use `_shared/` for common fixtures, database conventions, exit-code rules, and shared JSON assertions
@@ -346,9 +352,9 @@ Required per-case structure:
- `预期输出` - `预期输出`
- `断言结论` - `断言结论`
Recommended case-title pattern: Case file naming pattern:
- `## case: <stable-slug>` - `<stable-slug>.md`
Authoring order: Authoring order:
@@ -382,7 +388,8 @@ Do not block v1 on these:
- The design phase is complete enough to start coding. - The design phase is complete enough to start coding.
- Avoid reopening major design questions unless implementation forces it. - Avoid reopening major design questions unless implementation forces it.
- The repository already has compiling binaries and working schema init. - The repository already has compiling binaries and working schema init.
- Finish the inbox test-plan docs before starting broad `orch` implementation. - The inbox test-plan docs are in place; keep them synchronized before and during broad `orch` implementation.
- inbox command test-plan folders use `README.md` as an index plus one file per case; keep any further structural changes consistent with the documented rules above.
- Preserve the separation: - Preserve the separation:
- `inbox` handles communication - `inbox` handles communication
- `orch` handles scheduling - `orch` handles scheduling
+9 -8
View File
@@ -9,15 +9,16 @@ It complements automated Go tests. The goal is not to restate implementation det
## Directory Rules ## Directory Rules
- one folder per command or shared area - one folder per command or shared area
- one `README.md` per folder - each folder keeps a `README.md` entrypoint
- no one-file-per-case sprawl - command folders use `README.md` as an index only
- each command test case lives in its own Markdown file named after the case slug
- no numeric test IDs - no numeric test IDs
- each case is identified by `path + case slug` - each command case is identified by its concrete file path
Recommended case heading pattern: Case file naming pattern:
```md ```text
## case: send-rejects-invalid-payload-json <case-slug>.md
``` ```
## Authoring Principles ## Authoring Principles
@@ -48,7 +49,7 @@ Unless a case says otherwise:
- `README.md`: global conventions and glossary - `README.md`: global conventions and glossary
- `_shared/README.md`: reusable fixtures, JSON assertions, exit codes, payload rules - `_shared/README.md`: reusable fixtures, JSON assertions, exit codes, payload rules
- `workflows/README.md`: cross-command end-to-end scenarios - `workflows/README.md`: cross-command end-to-end scenarios
- per-command folders: command-specific cases and edge conditions - per-command folders: command-specific index `README.md` files plus one case document per test case
## Glossary ## Glossary
@@ -66,5 +67,5 @@ The current best executable reference is [internal/cli/inbox/integration_test.go
When this Markdown plan is expanded: When this Markdown plan is expanded:
- prefer matching an existing automated scenario first - prefer matching an existing automated scenario first
- record any additional manual-only contract coverage explicitly in the relevant command document - record any additional manual-only contract coverage explicitly in the relevant command case file and keep the folder index synchronized
- keep `docs/tests/inbox/ROADMAP.md` synchronized with authored files and case slugs - keep `docs/tests/inbox/ROADMAP.md` synchronized with authored files and case slugs
+171 -90
View File
@@ -25,12 +25,14 @@ Current state:
- `inbox` CLI is implemented end-to-end - `inbox` CLI is implemented end-to-end
- automated Go integration tests already exist for the main lifecycle, wait flows, unread behavior, artifacts, and JSON error contracts - automated Go integration tests already exist for the main lifecycle, wait flows, unread behavior, artifacts, and JSON error contracts
- this roadmap now exists under `docs/tests/inbox/ROADMAP.md` - this roadmap now exists under `docs/tests/inbox/ROADMAP.md`
- all planned global, shared, workflow, and command-level Markdown test-plan documents have been authored - all planned global, shared, workflow, command-index, and command-case Markdown documents have been authored
- command-level documents have been audited once per command against current CLI and store behavior, with edge-contract notes added for defaults, fallbacks, and error boundaries where needed
- every inbox command folder now uses `README.md` as an index plus one Markdown file per case
Progress summary for planned test-plan documents, excluding `ROADMAP.md`: Progress summary for planned test-plan documents, excluding `ROADMAP.md`:
- planned document files: `17` - planned document files: `70`
- authored document files: `17` - authored document files: `70`
- planned case slugs in this roadmap: `61` - planned case slugs in this roadmap: `61`
- authored case slugs in this roadmap: `61` - authored case slugs in this roadmap: `61`
@@ -66,20 +68,29 @@ Out of scope:
Directory model: Directory model:
- one folder per command or shared area - one folder per command or shared area
- one `README.md` per folder - each folder keeps a `README.md` entrypoint
- no one-file-per-case sprawl - command folders use `README.md` as an index only
- each command case lives in its own Markdown file named after the case slug
- cross-command workflow cases remain grouped in `docs/tests/inbox/workflows/README.md`
Case identity: Case identity:
- do not use numeric IDs - do not use numeric IDs
- identify a case by `path + case slug` - identify each command case by its concrete file path
- recommended heading pattern inside the command document: - identify each workflow case by `path + case slug`
- command case file naming pattern:
```text
<case-slug>.md
```
- workflow case heading pattern:
```md ```md
## case: send-rejects-invalid-payload-json ## case: send-rejects-invalid-payload-json
``` ```
Per-case structure inside the command document: Per-case structure inside the case document:
- `用例意义` - `用例意义`
- `前置条件` - `前置条件`
@@ -89,10 +100,12 @@ Per-case structure inside the command document:
How to update this roadmap when a new case is written: How to update this roadmap when a new case is written:
1. add the case content to the target `README.md` 1. if it is a command case, create or update the target `<case-slug>.md` file under the relevant command folder
2. move the case slug from `Pending Case Backlog` to `Authored Case Register` 2. if it is a command case, add or update the entry in that folder `README.md` index
3. update the authored counts in `Current Snapshot` 3. if it is a workflow case, add or update the case inside `docs/tests/inbox/workflows/README.md`
4. if a whole command document is created, update `Document Progress` 4. move the case slug from `Pending Case Backlog` to `Authored Case Register`
5. update the authored counts in `Current Snapshot`
6. if a new Markdown file is created, update `Document Progress`
Allowed status values in this roadmap: Allowed status values in this roadmap:
@@ -126,32 +139,46 @@ docs/tests/inbox/
README.md README.md
init/ init/
README.md README.md
<case-slug>.md
send/ send/
README.md README.md
<case-slug>.md
fetch/ fetch/
README.md README.md
<case-slug>.md
claim/ claim/
README.md README.md
<case-slug>.md
renew/ renew/
README.md README.md
<case-slug>.md
update/ update/
README.md README.md
<case-slug>.md
reply/ reply/
README.md README.md
<case-slug>.md
done/ done/
README.md README.md
<case-slug>.md
fail/ fail/
README.md README.md
<case-slug>.md
cancel/ cancel/
README.md README.md
<case-slug>.md
list/ list/
README.md README.md
<case-slug>.md
show/ show/
README.md README.md
<case-slug>.md
watch/ watch/
README.md README.md
<case-slug>.md
wait-reply/ wait-reply/
README.md README.md
<case-slug>.md
``` ```
## Document Progress ## Document Progress
@@ -161,20 +188,73 @@ docs/tests/inbox/
| `docs/tests/inbox/README.md` | Global testing conventions and glossary | 0 | 0 | done | | `docs/tests/inbox/README.md` | Global testing conventions and glossary | 0 | 0 | done |
| `docs/tests/inbox/_shared/README.md` | Shared fixtures, JSON assertions, exit-code rules | 0 | 0 | done | | `docs/tests/inbox/_shared/README.md` | Shared fixtures, JSON assertions, exit-code rules | 0 | 0 | done |
| `docs/tests/inbox/workflows/README.md` | Cross-command scenarios | 8 | 8 | done | | `docs/tests/inbox/workflows/README.md` | Cross-command scenarios | 8 | 8 | done |
| `docs/tests/inbox/init/README.md` | `init` command cases | 2 | 2 | done | | `docs/tests/inbox/init/README.md` | `init` command case index | 0 | 0 | done |
| `docs/tests/inbox/send/README.md` | `send` command cases | 6 | 6 | done | | `docs/tests/inbox/init/init-creates-schema-on-empty-db.md` | `init` command case | 1 | 1 | done |
| `docs/tests/inbox/fetch/README.md` | `fetch` command cases | 4 | 4 | done | | `docs/tests/inbox/init/init-is-idempotent-on-existing-db.md` | `init` command case | 1 | 1 | done |
| `docs/tests/inbox/claim/README.md` | `claim` command cases | 4 | 4 | done | | `docs/tests/inbox/send/README.md` | `send` command case index | 0 | 0 | done |
| `docs/tests/inbox/renew/README.md` | `renew` command cases | 3 | 3 | done | | `docs/tests/inbox/send/send-creates-new-thread.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/update/README.md` | `update` command cases | 5 | 5 | done | | `docs/tests/inbox/send/send-appends-message-to-existing-thread.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/reply/README.md` | `reply` command cases | 4 | 4 | done | | `docs/tests/inbox/send/send-reads-body-from-body-file.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/done/README.md` | `done` command cases | 4 | 4 | done | | `docs/tests/inbox/send/send-attaches-artifact-with-metadata.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/fail/README.md` | `fail` command cases | 4 | 4 | done | | `docs/tests/inbox/send/send-rejects-invalid-payload-json.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/cancel/README.md` | `cancel` command cases | 3 | 3 | done | | `docs/tests/inbox/send/send-rejects-invalid-artifact-metadata-json.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/list/README.md` | `list` command cases | 4 | 4 | done | | `docs/tests/inbox/fetch/README.md` | `fetch` command case index | 0 | 0 | done |
| `docs/tests/inbox/show/README.md` | `show` command cases | 4 | 4 | done | | `docs/tests/inbox/fetch/fetch-returns-pending-thread-for-target-agent.md` | `fetch` command case | 1 | 1 | done |
| `docs/tests/inbox/watch/README.md` | `watch` command cases | 3 | 3 | done | | `docs/tests/inbox/fetch/fetch-respects-status-and-limit-filters.md` | `fetch` command case | 1 | 1 | done |
| `docs/tests/inbox/wait-reply/README.md` | `wait-reply` command cases | 3 | 3 | done | | `docs/tests/inbox/fetch/fetch-unread-uses-read-cursor.md` | `fetch` command case | 1 | 1 | done |
| `docs/tests/inbox/fetch/fetch-returns-no-matching-work-when-empty.md` | `fetch` command case | 1 | 1 | done |
| `docs/tests/inbox/claim/README.md` | `claim` command case index | 0 | 0 | done |
| `docs/tests/inbox/claim/claim-acquires-thread-lease.md` | `claim` command case | 1 | 1 | done |
| `docs/tests/inbox/claim/claim-rejects-when-thread-missing.md` | `claim` command case | 1 | 1 | done |
| `docs/tests/inbox/claim/claim-rejects-when-thread-already-claimed.md` | `claim` command case | 1 | 1 | done |
| `docs/tests/inbox/claim/claim-records-requested-lease-duration.md` | `claim` command case | 1 | 1 | done |
| `docs/tests/inbox/renew/README.md` | `renew` command case index | 0 | 0 | done |
| `docs/tests/inbox/renew/renew-extends-active-lease.md` | `renew` command case | 1 | 1 | done |
| `docs/tests/inbox/renew/renew-rejects-non-owner.md` | `renew` command case | 1 | 1 | done |
| `docs/tests/inbox/renew/renew-rejects-without-active-lease.md` | `renew` command case | 1 | 1 | done |
| `docs/tests/inbox/update/README.md` | `update` command case index | 0 | 0 | done |
| `docs/tests/inbox/update/update-moves-thread-to-in-progress.md` | `update` command case | 1 | 1 | done |
| `docs/tests/inbox/update/update-moves-thread-to-blocked-with-payload.md` | `update` command case | 1 | 1 | done |
| `docs/tests/inbox/update/update-accepts-body-file-and-artifact.md` | `update` command case | 1 | 1 | done |
| `docs/tests/inbox/update/update-rejects-invalid-payload-json.md` | `update` command case | 1 | 1 | done |
| `docs/tests/inbox/update/update-rejects-non-owner.md` | `update` command case | 1 | 1 | done |
| `docs/tests/inbox/reply/README.md` | `reply` command case index | 0 | 0 | done |
| `docs/tests/inbox/reply/reply-adds-answer-message.md` | `reply` command case | 1 | 1 | done |
| `docs/tests/inbox/reply/reply-supports-control-kind.md` | `reply` command case | 1 | 1 | done |
| `docs/tests/inbox/reply/reply-attaches-artifact.md` | `reply` command case | 1 | 1 | done |
| `docs/tests/inbox/reply/reply-rejects-invalid-payload-json.md` | `reply` command case | 1 | 1 | done |
| `docs/tests/inbox/done/README.md` | `done` command case index | 0 | 0 | done |
| `docs/tests/inbox/done/done-marks-thread-terminal.md` | `done` command case | 1 | 1 | done |
| `docs/tests/inbox/done/done-persists-result-body-and-artifact.md` | `done` command case | 1 | 1 | done |
| `docs/tests/inbox/done/done-rejects-non-owner.md` | `done` command case | 1 | 1 | done |
| `docs/tests/inbox/done/done-rejects-on-terminal-thread.md` | `done` command case | 1 | 1 | done |
| `docs/tests/inbox/fail/README.md` | `fail` command case index | 0 | 0 | done |
| `docs/tests/inbox/fail/fail-marks-thread-failed.md` | `fail` command case | 1 | 1 | done |
| `docs/tests/inbox/fail/fail-persists-failure-body-and-artifact.md` | `fail` command case | 1 | 1 | done |
| `docs/tests/inbox/fail/fail-rejects-non-owner.md` | `fail` command case | 1 | 1 | done |
| `docs/tests/inbox/fail/fail-rejects-on-terminal-thread.md` | `fail` command case | 1 | 1 | done |
| `docs/tests/inbox/cancel/README.md` | `cancel` command case index | 0 | 0 | done |
| `docs/tests/inbox/cancel/cancel-marks-thread-cancelled.md` | `cancel` command case | 1 | 1 | done |
| `docs/tests/inbox/cancel/cancel-persists-reason-and-artifact.md` | `cancel` command case | 1 | 1 | done |
| `docs/tests/inbox/cancel/cancel-rejects-when-thread-missing.md` | `cancel` command case | 1 | 1 | done |
| `docs/tests/inbox/list/README.md` | `list` command case index | 0 | 0 | done |
| `docs/tests/inbox/list/list-filters-by-status.md` | `list` command case | 1 | 1 | done |
| `docs/tests/inbox/list/list-filters-by-created-by.md` | `list` command case | 1 | 1 | done |
| `docs/tests/inbox/list/list-filters-by-assigned-to.md` | `list` command case | 1 | 1 | done |
| `docs/tests/inbox/list/list-respects-limit.md` | `list` command case | 1 | 1 | done |
| `docs/tests/inbox/show/README.md` | `show` command case index | 0 | 0 | done |
| `docs/tests/inbox/show/show-returns-thread-and-message-history.md` | `show` command case | 1 | 1 | done |
| `docs/tests/inbox/show/show-includes-artifacts-per-message.md` | `show` command case | 1 | 1 | done |
| `docs/tests/inbox/show/show-mark-read-advances-read-cursor.md` | `show` command case | 1 | 1 | done |
| `docs/tests/inbox/show/show-rejects-when-thread-missing.md` | `show` command case | 1 | 1 | done |
| `docs/tests/inbox/watch/README.md` | `watch` command case index | 0 | 0 | done |
| `docs/tests/inbox/watch/watch-wakes-on-matching-thread.md` | `watch` command case | 1 | 1 | done |
| `docs/tests/inbox/watch/watch-respects-status-filter.md` | `watch` command case | 1 | 1 | done |
| `docs/tests/inbox/watch/watch-times-out-with-no-activity.md` | `watch` command case | 1 | 1 | done |
| `docs/tests/inbox/wait-reply/README.md` | `wait-reply` command case index | 0 | 0 | done |
| `docs/tests/inbox/wait-reply/wait-reply-wakes-on-answer-after-message.md` | `wait-reply` command case | 1 | 1 | done |
| `docs/tests/inbox/wait-reply/wait-reply-can-start-from-after-event.md` | `wait-reply` command case | 1 | 1 | done |
| `docs/tests/inbox/wait-reply/wait-reply-times-out-when-no-reply.md` | `wait-reply` command case | 1 | 1 | done |
## Authoring Order ## Authoring Order
@@ -183,13 +263,13 @@ Recommended order:
1. `docs/tests/inbox/README.md` 1. `docs/tests/inbox/README.md`
2. `docs/tests/inbox/_shared/README.md` 2. `docs/tests/inbox/_shared/README.md`
3. `docs/tests/inbox/workflows/README.md` 3. `docs/tests/inbox/workflows/README.md`
4. `docs/tests/inbox/send/README.md` 4. `docs/tests/inbox/send/README.md` plus its linked case files
5. `docs/tests/inbox/fetch/README.md` 5. `docs/tests/inbox/fetch/README.md` plus its linked case files
6. `docs/tests/inbox/claim/README.md` 6. `docs/tests/inbox/claim/README.md` plus its linked case files
7. `docs/tests/inbox/reply/README.md` 7. `docs/tests/inbox/reply/README.md` plus its linked case files
8. `docs/tests/inbox/done/README.md` 8. `docs/tests/inbox/done/README.md` plus its linked case files
9. `docs/tests/inbox/show/README.md` 9. `docs/tests/inbox/show/README.md` plus its linked case files
10. the remaining command documents 10. the remaining command indexes and case files
Reason: Reason:
@@ -208,59 +288,59 @@ Reason:
| `docs/tests/inbox/workflows/README.md` | `artifact-visible-through-send-and-show` | body-file and artifact data survive send and show | done | | `docs/tests/inbox/workflows/README.md` | `artifact-visible-through-send-and-show` | body-file and artifact data survive send and show | done |
| `docs/tests/inbox/workflows/README.md` | `unread-clears-after-mark-read-and-reappears-on-new-message` | read cursor clears unread and new message restores it | done | | `docs/tests/inbox/workflows/README.md` | `unread-clears-after-mark-read-and-reappears-on-new-message` | read cursor clears unread and new message restores it | done |
| `docs/tests/inbox/workflows/README.md` | `wait-reply-clears-blocked-unread-for-agent` | wait-reply consumes reply and clears blocked unread view | done | | `docs/tests/inbox/workflows/README.md` | `wait-reply-clears-blocked-unread-for-agent` | wait-reply consumes reply and clears blocked unread view | done |
| `docs/tests/inbox/init/README.md` | `init-creates-schema-on-empty-db` | initializes an empty database path and returns initialized status | done | | `docs/tests/inbox/init/init-creates-schema-on-empty-db.md` | `init-creates-schema-on-empty-db` | initializes an empty database path and returns initialized status | done |
| `docs/tests/inbox/init/README.md` | `init-is-idempotent-on-existing-db` | repeated init succeeds on the same database path | done | | `docs/tests/inbox/init/init-is-idempotent-on-existing-db.md` | `init-is-idempotent-on-existing-db` | repeated init succeeds on the same database path | done |
| `docs/tests/inbox/send/README.md` | `send-creates-new-thread` | creates a pending thread with an initial task message | done | | `docs/tests/inbox/send/send-creates-new-thread.md` | `send-creates-new-thread` | creates a pending thread with an initial task message | done |
| `docs/tests/inbox/send/README.md` | `send-appends-message-to-existing-thread` | appends a message to an existing non-terminal thread | done | | `docs/tests/inbox/send/send-appends-message-to-existing-thread.md` | `send-appends-message-to-existing-thread` | appends a message to an existing non-terminal thread | done |
| `docs/tests/inbox/send/README.md` | `send-reads-body-from-body-file` | reads message body from a file path | done | | `docs/tests/inbox/send/send-reads-body-from-body-file.md` | `send-reads-body-from-body-file` | reads message body from a file path | done |
| `docs/tests/inbox/send/README.md` | `send-attaches-artifact-with-metadata` | persists artifact path, kind, and metadata on send | done | | `docs/tests/inbox/send/send-attaches-artifact-with-metadata.md` | `send-attaches-artifact-with-metadata` | persists artifact path, kind, and metadata on send | done |
| `docs/tests/inbox/send/README.md` | `send-rejects-invalid-payload-json` | rejects malformed payload JSON with invalid_input | done | | `docs/tests/inbox/send/send-rejects-invalid-payload-json.md` | `send-rejects-invalid-payload-json` | rejects malformed payload JSON with `invalid_input` | done |
| `docs/tests/inbox/send/README.md` | `send-rejects-invalid-artifact-metadata-json` | rejects malformed artifact metadata JSON | done | | `docs/tests/inbox/send/send-rejects-invalid-artifact-metadata-json.md` | `send-rejects-invalid-artifact-metadata-json` | rejects malformed artifact metadata JSON | done |
| `docs/tests/inbox/fetch/README.md` | `fetch-returns-pending-thread-for-target-agent` | returns pending candidate work for the target agent | done | | `docs/tests/inbox/fetch/fetch-returns-pending-thread-for-target-agent.md` | `fetch-returns-pending-thread-for-target-agent` | returns pending candidate work for the target agent | done |
| `docs/tests/inbox/fetch/README.md` | `fetch-respects-status-and-limit-filters` | enforces status filtering and max row count | done | | `docs/tests/inbox/fetch/fetch-respects-status-and-limit-filters.md` | `fetch-respects-status-and-limit-filters` | enforces status filtering and max row count | done |
| `docs/tests/inbox/fetch/README.md` | `fetch-unread-uses-read-cursor` | unread filtering depends on per-agent read cursor state | done | | `docs/tests/inbox/fetch/fetch-unread-uses-read-cursor.md` | `fetch-unread-uses-read-cursor` | unread filtering depends on per-agent read cursor state | done |
| `docs/tests/inbox/fetch/README.md` | `fetch-returns-no-matching-work-when-empty` | empty fetch result returns no_matching_work | done | | `docs/tests/inbox/fetch/fetch-returns-no-matching-work-when-empty.md` | `fetch-returns-no-matching-work-when-empty` | empty fetch result returns no_matching_work | done |
| `docs/tests/inbox/claim/README.md` | `claim-acquires-thread-lease` | claims a pending thread and records a claim event message | done | | `docs/tests/inbox/claim/claim-acquires-thread-lease.md` | `claim-acquires-thread-lease` | claims a pending thread and records a claim event message | done |
| `docs/tests/inbox/claim/README.md` | `claim-rejects-when-thread-missing` | missing thread returns not_found | done | | `docs/tests/inbox/claim/claim-rejects-when-thread-missing.md` | `claim-rejects-when-thread-missing` | missing thread returns not_found | done |
| `docs/tests/inbox/claim/README.md` | `claim-rejects-when-thread-already-claimed` | active lease conflict returns lease_conflict | done | | `docs/tests/inbox/claim/claim-rejects-when-thread-already-claimed.md` | `claim-rejects-when-thread-already-claimed` | active lease conflict returns lease_conflict | done |
| `docs/tests/inbox/claim/README.md` | `claim-records-requested-lease-duration` | claim event payload records requested lease duration | done | | `docs/tests/inbox/claim/claim-records-requested-lease-duration.md` | `claim-records-requested-lease-duration` | claim event payload records requested lease duration | done |
| `docs/tests/inbox/renew/README.md` | `renew-extends-active-lease` | owner renews an active lease and gets a renewal event | done | | `docs/tests/inbox/renew/renew-extends-active-lease.md` | `renew-extends-active-lease` | owner renews an active lease and gets a renewal event | done |
| `docs/tests/inbox/renew/README.md` | `renew-rejects-non-owner` | non-owner renew attempt returns lease_conflict | done | | `docs/tests/inbox/renew/renew-rejects-non-owner.md` | `renew-rejects-non-owner` | non-owner renew attempt returns lease_conflict | done |
| `docs/tests/inbox/renew/README.md` | `renew-rejects-without-active-lease` | missing active lease returns invalid_state | done | | `docs/tests/inbox/renew/renew-rejects-without-active-lease.md` | `renew-rejects-without-active-lease` | missing active lease returns invalid_state | done |
| `docs/tests/inbox/update/README.md` | `update-moves-thread-to-in-progress` | owner moves a thread into in_progress with a progress message | done | | `docs/tests/inbox/update/update-moves-thread-to-in-progress.md` | `update-moves-thread-to-in-progress` | moves a claimed thread to `in_progress` and emits a progress message | done |
| `docs/tests/inbox/update/README.md` | `update-moves-thread-to-blocked-with-payload` | blocked update writes a question message and payload JSON | done | | `docs/tests/inbox/update/update-moves-thread-to-blocked-with-payload.md` | `update-moves-thread-to-blocked-with-payload` | moves a claimed thread to `blocked` with structured question payload | done |
| `docs/tests/inbox/update/README.md` | `update-accepts-body-file-and-artifact` | update supports body-file input and artifact attachments | done | | `docs/tests/inbox/update/update-accepts-body-file-and-artifact.md` | `update-accepts-body-file-and-artifact` | persists update body from file plus artifacts | done |
| `docs/tests/inbox/update/README.md` | `update-rejects-invalid-payload-json` | malformed update payload returns invalid_input | done | | `docs/tests/inbox/update/update-rejects-invalid-payload-json.md` | `update-rejects-invalid-payload-json` | rejects malformed `--payload-json` input | done |
| `docs/tests/inbox/update/README.md` | `update-rejects-non-owner` | non-owner update attempt returns lease_conflict | done | | `docs/tests/inbox/update/update-rejects-non-owner.md` | `update-rejects-non-owner` | rejects update when caller is not the active lease owner | done |
| `docs/tests/inbox/reply/README.md` | `reply-adds-answer-message` | default reply kind is answer and thread stays non-terminal | done | | `docs/tests/inbox/reply/reply-adds-answer-message.md` | `reply-adds-answer-message` | appends default `answer` message to an existing non-terminal thread | done |
| `docs/tests/inbox/reply/README.md` | `reply-supports-control-kind` | reply can explicitly send control messages | done | | `docs/tests/inbox/reply/reply-supports-control-kind.md` | `reply-supports-control-kind` | supports explicit `--kind control` reply message | done |
| `docs/tests/inbox/reply/README.md` | `reply-attaches-artifact` | reply persists artifact data on the message | done | | `docs/tests/inbox/reply/reply-attaches-artifact.md` | `reply-attaches-artifact` | appends reply message with artifact payload | done |
| `docs/tests/inbox/reply/README.md` | `reply-rejects-invalid-payload-json` | malformed reply payload returns invalid_input | done | | `docs/tests/inbox/reply/reply-rejects-invalid-payload-json.md` | `reply-rejects-invalid-payload-json` | rejects malformed `--payload-json` input | done |
| `docs/tests/inbox/done/README.md` | `done-marks-thread-terminal` | owner completes a thread into done terminal state | done | | `docs/tests/inbox/done/done-marks-thread-terminal.md` | `done-marks-thread-terminal` | marks a claimed thread as `done` with a result message | done |
| `docs/tests/inbox/done/README.md` | `done-persists-result-body-and-artifact` | result body and artifacts remain visible after done | done | | `docs/tests/inbox/done/done-persists-result-body-and-artifact.md` | `done-persists-result-body-and-artifact` | persists result body and artifact for follow-up reads | done |
| `docs/tests/inbox/done/README.md` | `done-rejects-non-owner` | non-owner done attempt returns lease_conflict | done | | `docs/tests/inbox/done/done-rejects-non-owner.md` | `done-rejects-non-owner` | rejects `done` from non-owner agent | done |
| `docs/tests/inbox/done/README.md` | `done-rejects-on-terminal-thread` | terminal threads reject repeated done calls | done | | `docs/tests/inbox/done/done-rejects-on-terminal-thread.md` | `done-rejects-on-terminal-thread` | rejects `done` on terminal thread states | done |
| `docs/tests/inbox/fail/README.md` | `fail-marks-thread-failed` | owner completes a thread into failed terminal state | done | | `docs/tests/inbox/fail/fail-marks-thread-failed.md` | `fail-marks-thread-failed` | marks a claimed thread as `failed` with a result message | done |
| `docs/tests/inbox/fail/README.md` | `fail-persists-failure-body-and-artifact` | failure body and artifacts remain visible after fail | done | | `docs/tests/inbox/fail/fail-persists-failure-body-and-artifact.md` | `fail-persists-failure-body-and-artifact` | persists failure body and artifacts for diagnosis | done |
| `docs/tests/inbox/fail/README.md` | `fail-rejects-non-owner` | non-owner fail attempt returns lease_conflict | done | | `docs/tests/inbox/fail/fail-rejects-non-owner.md` | `fail-rejects-non-owner` | rejects `fail` from non-owner agent | done |
| `docs/tests/inbox/fail/README.md` | `fail-rejects-on-terminal-thread` | terminal threads reject repeated fail calls | done | | `docs/tests/inbox/fail/fail-rejects-on-terminal-thread.md` | `fail-rejects-on-terminal-thread` | rejects `fail` on terminal thread states | done |
| `docs/tests/inbox/cancel/README.md` | `cancel-marks-thread-cancelled` | cancel moves a non-terminal thread to cancelled | done | | `docs/tests/inbox/cancel/cancel-marks-thread-cancelled.md` | `cancel-marks-thread-cancelled` | moves a non-terminal thread into `cancelled` and emits a control message | done |
| `docs/tests/inbox/cancel/README.md` | `cancel-persists-reason-and-artifact` | cancel records its reason text and attachments | done | | `docs/tests/inbox/cancel/cancel-persists-reason-and-artifact.md` | `cancel-persists-reason-and-artifact` | persists cancel reason text and attached artifacts | done |
| `docs/tests/inbox/cancel/README.md` | `cancel-rejects-when-thread-missing` | missing thread returns not_found on cancel | done | | `docs/tests/inbox/cancel/cancel-rejects-when-thread-missing.md` | `cancel-rejects-when-thread-missing` | returns stable not-found contract when thread does not exist | done |
| `docs/tests/inbox/list/README.md` | `list-filters-by-status` | status filter limits listed threads | done | | `docs/tests/inbox/list/list-filters-by-status.md` | `list-filters-by-status` | filters returned threads by status set | done |
| `docs/tests/inbox/list/README.md` | `list-filters-by-created-by` | created-by filter limits listed threads | done | | `docs/tests/inbox/list/list-filters-by-created-by.md` | `list-filters-by-created-by` | filters returned threads by creator | done |
| `docs/tests/inbox/list/README.md` | `list-filters-by-assigned-to` | assigned-to filter limits listed threads | done | | `docs/tests/inbox/list/list-filters-by-assigned-to.md` | `list-filters-by-assigned-to` | filters returned threads by current assignee | done |
| `docs/tests/inbox/list/README.md` | `list-respects-limit` | list returns at most the requested number of rows | done | | `docs/tests/inbox/list/list-respects-limit.md` | `list-respects-limit` | enforces hard cap on returned thread count | done |
| `docs/tests/inbox/show/README.md` | `show-returns-thread-and-message-history` | show returns thread metadata and ordered history | done | | `docs/tests/inbox/show/show-returns-thread-and-message-history.md` | `show-returns-thread-and-message-history` | returns thread details and full time-ordered message history | done |
| `docs/tests/inbox/show/README.md` | `show-includes-artifacts-per-message` | show expands message artifacts in detail view | done | | `docs/tests/inbox/show/show-includes-artifacts-per-message.md` | `show-includes-artifacts-per-message` | expands per-message artifacts in the show payload | done |
| `docs/tests/inbox/show/README.md` | `show-mark-read-advances-read-cursor` | mark-read changes subsequent unread visibility | done | | `docs/tests/inbox/show/show-mark-read-advances-read-cursor.md` | `show-mark-read-advances-read-cursor` | advances caller read cursor when `--mark-read` is used | done |
| `docs/tests/inbox/show/README.md` | `show-rejects-when-thread-missing` | missing thread returns not_found on show | done | | `docs/tests/inbox/show/show-rejects-when-thread-missing.md` | `show-rejects-when-thread-missing` | returns stable not-found contract for missing thread | done |
| `docs/tests/inbox/watch/README.md` | `watch-wakes-on-matching-thread` | watch wakes on a new matching thread event | done | | `docs/tests/inbox/watch/watch-wakes-on-matching-thread.md` | `watch-wakes-on-matching-thread` | wakes when a matching post-start event arrives and returns event context | done |
| `docs/tests/inbox/watch/README.md` | `watch-respects-status-filter` | watch only wakes on events whose resulting thread status matches | done | | `docs/tests/inbox/watch/watch-respects-status-filter.md` | `watch-respects-status-filter` | wakes only when thread transitions into requested status | done |
| `docs/tests/inbox/watch/README.md` | `watch-times-out-with-no-activity` | watch timeout returns no_matching_work | done | | `docs/tests/inbox/watch/watch-times-out-with-no-activity.md` | `watch-times-out-with-no-activity` | returns timeout contract when no matching activity arrives | done |
| `docs/tests/inbox/wait-reply/README.md` | `wait-reply-wakes-on-answer-after-message` | wait-reply resumes from a known message boundary | done | | `docs/tests/inbox/wait-reply/wait-reply-wakes-on-answer-after-message.md` | `wait-reply-wakes-on-answer-after-message` | wakes for a qualifying reply after known message boundary | done |
| `docs/tests/inbox/wait-reply/README.md` | `wait-reply-can-start-from-after-event` | wait-reply resumes from a known event cursor | done | | `docs/tests/inbox/wait-reply/wait-reply-can-start-from-after-event.md` | `wait-reply-can-start-from-after-event` | resumes waiting from a known event cursor | done |
| `docs/tests/inbox/wait-reply/README.md` | `wait-reply-times-out-when-no-reply` | wait-reply timeout returns no_matching_work | done | | `docs/tests/inbox/wait-reply/wait-reply-times-out-when-no-reply.md` | `wait-reply-times-out-when-no-reply` | returns timeout contract when no qualifying reply arrives | done |
## Pending Case Backlog ## Pending Case Backlog
@@ -268,16 +348,17 @@ No pending case slugs remain in the current plan.
When a new CLI contract or workflow needs coverage: When a new CLI contract or workflow needs coverage:
1. add the new case to the relevant `README.md` 1. if it is a command case, create a new `<case-slug>.md` file under the relevant command folder and add it to that folder `README.md` index
2. add the new slug to `Authored Case Register` 2. if it is a workflow case, add it to `docs/tests/inbox/workflows/README.md`
3. update `Current Snapshot` and `Document Progress` 3. add the new slug to `Authored Case Register`
4. update `Current Snapshot` and `Document Progress`
## Definition Of Done ## Definition Of Done
This roadmap is complete only when all of the following are true: This roadmap is complete only when all of the following are true:
- every implemented inbox command has a corresponding document folder - every implemented inbox command has a corresponding document folder
- each planned command document exists - each planned command index and case document exists
- each pending case slug has been either authored or explicitly deferred - each pending case slug has been either authored or explicitly deferred
- the authored-case register matches the actual Markdown files on disk - the authored-case register matches the actual Markdown files on disk
- a new agent can pick any pending case and know exactly where it should be written - a new agent can pick any pending case and know exactly where it should be written
+7 -85
View File
@@ -1,87 +1,9 @@
# Inbox `cancel` Test Plan # Inbox `cancel` Test Plan Index
## Scope ## Case Files
This document covers thread cancellation via `inbox cancel`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `cancel-marks-thread-cancelled` | [cancel-marks-thread-cancelled.md](./cancel-marks-thread-cancelled.md) | moves a non-terminal thread into `cancelled` and emits a control message |
| `cancel-persists-reason-and-artifact` | [cancel-persists-reason-and-artifact.md](./cancel-persists-reason-and-artifact.md) | persists cancel reason text and attached artifacts |
## case: cancel-marks-thread-cancelled | `cancel-rejects-when-thread-missing` | [cancel-rejects-when-thread-missing.md](./cancel-rejects-when-thread-missing.md) | returns stable not-found contract when thread does not exist |
### 用例意义
验证 `cancel` 可以把非终态线程推进到 `cancelled` 终态,并生成控制消息。
### 前置条件
- 已存在一个非终态线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json cancel --agent leader --thread THREAD_ID --reason "Task superseded by a larger refactor"
```
### 预期输出
- 命令退出码为 `0`
- `thread.status == "cancelled"`
- `message.kind == "control"`
### 断言结论
- `cancel` 是线程级终态转换
- 取消时会释放活跃 lease
## case: cancel-persists-reason-and-artifact
### 用例意义
验证 `cancel` 的原因文本与附件会被完整持久化。
### 前置条件
- 已存在一个非终态线程 `THREAD_ID`
- `TMPDIR/cancel.md` 已存在
### 输入
```bash
inbox --db TMPDIR/coord.db --json cancel --agent leader --thread THREAD_ID --reason "Task superseded by a larger refactor" --artifact TMPDIR/cancel.md --artifact-kind brief
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `cancel` 成功
- 取消消息 `summary``body` 都保留取消原因
- 取消消息包含 1 个 artifact
### 断言结论
- `cancel` 既保留人类可读原因,也支持附带上下文材料
## case: cancel-rejects-when-thread-missing
### 用例意义
验证 `cancel` 对不存在线程返回稳定的 not-found 错误契约。
### 前置条件
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json cancel --agent leader --thread thr_missing
```
### 预期输出
- 退出码为 `40`
- JSON 错误码为 `not_found`
### 断言结论
- `cancel` 不会为缺失线程隐式创建控制消息
@@ -0,0 +1,29 @@
# case: cancel-marks-thread-cancelled
### 用例意义
验证 `cancel` 可以把非终态线程推进到 `cancelled` 终态,并生成控制消息。
### 前置条件
- 已存在一个非终态线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json cancel --agent leader --thread THREAD_ID --reason "Task superseded by a larger refactor"
```
### 预期输出
- 命令退出码为 `0`
- `thread.status == "cancelled"`
- `message.kind == "control"`
### 断言结论
- `cancel` 是线程级终态转换
- 取消时会释放活跃 lease
- `cancel` 不要求调用方持有活跃 lease;只要线程存在且尚未进入终态,就可以被取消
- 如果线程已经是 `done``failed``cancelled`,应返回 `invalid_state`,而不是 `lease_conflict`
@@ -0,0 +1,30 @@
# case: cancel-persists-reason-and-artifact
### 用例意义
验证 `cancel` 的原因文本与附件会被完整持久化。
### 前置条件
- 已存在一个非终态线程 `THREAD_ID`
- `TMPDIR/cancel.md` 已存在
### 输入
```bash
inbox --db TMPDIR/coord.db --json cancel --agent leader --thread THREAD_ID --reason "Task superseded by a larger refactor" --artifact TMPDIR/cancel.md --artifact-kind brief
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `cancel` 成功
- 取消消息 `summary``body` 都保留取消原因
- 取消消息包含 1 个 artifact
### 断言结论
- `cancel` 既保留人类可读原因,也支持附带上下文材料
-`--reason` 为空时,取消消息的 `summary` 会回退为 `thread cancelled`,而 `body` 保持空字符串
- `--artifact-kind``--artifact-metadata-json` 需要至少一个 `--artifact`,且多值数量必须是 `1` 或与 artifact 数量一致;否则应返回 `invalid_input`
@@ -0,0 +1,27 @@
# case: cancel-rejects-when-thread-missing
### 用例意义
验证 `cancel` 对不存在线程返回稳定的 not-found 错误契约。
### 前置条件
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json cancel --agent leader --thread thr_missing
```
### 预期输出
- 退出码为 `40`
- JSON 错误码为 `not_found`
### 断言结论
- `cancel` 不会为缺失线程隐式创建控制消息
- 当命令级 `--agent` 未显式提供时,可以回退使用根级 `--agent`;两者都缺失时应返回 `invalid_input`
- `--thread` 是必填 flag;缺失时属于 `invalid_input` 类 usage error
+8 -110
View File
@@ -1,112 +1,10 @@
# Inbox `claim` Test Plan # Inbox `claim` Test Plan Index
## Scope ## Case Files
This document covers lease acquisition via `inbox claim`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `claim-acquires-thread-lease` | [claim-acquires-thread-lease.md](./claim-acquires-thread-lease.md) | claims a pending thread and records a claim event message |
| `claim-rejects-when-thread-missing` | [claim-rejects-when-thread-missing.md](./claim-rejects-when-thread-missing.md) | missing thread returns not_found |
## case: claim-acquires-thread-lease | `claim-rejects-when-thread-already-claimed` | [claim-rejects-when-thread-already-claimed.md](./claim-rejects-when-thread-already-claimed.md) | active lease conflict returns lease_conflict |
| `claim-records-requested-lease-duration` | [claim-records-requested-lease-duration.md](./claim-records-requested-lease-duration.md) | claim event payload records requested lease duration |
### 用例意义
验证 `claim` 可以把 `pending` 线程切换到 `claimed`,并生成租约事件消息。
### 前置条件
- 已存在一个 `pending` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID --lease-seconds 300
```
### 预期输出
- 命令退出码为 `0`
- `thread.status == "claimed"`
- `thread.assigned_to == "worker-a"`
- `message.kind == "event"`
- `message.summary == "thread claimed"`
### 断言结论
- `claim` 同时更新线程状态与活跃租约
- 成功领取会附带一条事件消息,而不是静默改状态
## case: claim-rejects-when-thread-missing
### 用例意义
验证 `claim` 对不存在的线程返回稳定的 not-found 错误契约。
### 前置条件
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json claim --agent worker-z --thread thr_missing
```
### 预期输出
- 退出码为 `40`
- JSON 错误码为 `not_found`
### 断言结论
- 缺失线程会被明确区分为引用错误,而不是 lease 冲突
## case: claim-rejects-when-thread-already-claimed
### 用例意义
验证同一线程在已有活跃租约时,其他执行者无法重复领取。
### 前置条件
- `worker-z` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json claim --agent worker-y --thread THREAD_ID
```
### 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
### 断言结论
- 活跃 lease 是 `claim` 的排他条件
## case: claim-records-requested-lease-duration
### 用例意义
验证 `claim --lease-seconds` 的请求值会进入事件消息 payload,便于后续审计。
### 前置条件
- 已存在一个 `pending` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID --lease-seconds 300
```
### 预期输出
- 命令退出码为 `0`
- `message.payload_json.lease_seconds == 300`
- `message.payload_json.lease_token` 存在
### 断言结论
- 请求的租约时长不是仅用于内部计算,也会被持久化到事件消息中
@@ -0,0 +1,33 @@
# Case: `claim-acquires-thread-lease`
## 用例意义
验证 `claim` 可以把 `pending` 线程切换到 `claimed`,并生成租约事件消息。
## 前置条件
- 已存在一个 `pending` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID --lease-seconds 300
```
## 预期输出
- 命令退出码为 `0`
- `thread.status == "claimed"`
- `thread.assigned_to == "worker-a"`
- `message.kind == "event"`
- `message.summary == "thread claimed"`
## 断言结论
- `claim` 同时更新线程状态与活跃租约
- 成功领取会附带一条事件消息,而不是静默改状态
- 未显式传 `--lease-seconds`,或传入非正值时,租约时长应回退到默认 `900`
## 补充约束
-`--agent` 未显式提供时,可以回退使用根级 `--agent`
@@ -0,0 +1,25 @@
# Case: `claim-records-requested-lease-duration`
## 用例意义
验证 `claim --lease-seconds` 的请求值会进入事件消息 payload,便于后续审计。
## 前置条件
- 已存在一个 `pending` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID --lease-seconds 300
```
## 预期输出
- 命令退出码为 `0`
- `message.payload_json.lease_seconds == 300`
- `message.payload_json.lease_token` 存在
## 断言结论
- 请求的租约时长不是仅用于内部计算,也会被持久化到事件消息中
@@ -0,0 +1,28 @@
# Case: `claim-rejects-when-thread-already-claimed`
## 用例意义
验证同一线程在已有活跃租约时,其他执行者无法重复领取。
## 前置条件
- `worker-z` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json claim --agent worker-y --thread THREAD_ID
```
## 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
## 断言结论
- 活跃 lease 是 `claim` 的排他条件
## 补充约束
- `claim` 只允许作用在 `pending` 线程上;如果线程已是 `claimed``in_progress``blocked`,或已进入任一终态,则应返回 `invalid_state`,而不是 `lease_conflict`
@@ -0,0 +1,28 @@
# Case: `claim-rejects-when-thread-missing`
## 用例意义
验证 `claim` 对不存在的线程返回稳定的 not-found 错误契约。
## 前置条件
- 空数据库已完成 `init`
## 输入
```bash
inbox --db TMPDIR/coord.db --json claim --agent worker-z --thread thr_missing
```
## 预期输出
- 退出码为 `40`
- JSON 错误码为 `not_found`
## 断言结论
- 缺失线程会被明确区分为引用错误,而不是 lease 冲突
## 补充约束
- `--thread` 是必填 flag;缺失时属于 `invalid_input` 类 usage error
+8 -110
View File
@@ -1,112 +1,10 @@
# Inbox `done` Test Plan # Inbox `done` Test Plan Index
## Scope ## Case Files
This document covers successful terminal completion via `inbox done`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `done-marks-thread-terminal` | [done-marks-thread-terminal.md](./done-marks-thread-terminal.md) | marks a claimed thread as `done` with a result message |
| `done-persists-result-body-and-artifact` | [done-persists-result-body-and-artifact.md](./done-persists-result-body-and-artifact.md) | persists result body and artifact for follow-up reads |
## case: done-marks-thread-terminal | `done-rejects-non-owner` | [done-rejects-non-owner.md](./done-rejects-non-owner.md) | rejects `done` from non-owner agent |
| `done-rejects-on-terminal-thread` | [done-rejects-on-terminal-thread.md](./done-rejects-on-terminal-thread.md) | rejects `done` on terminal thread states |
### 用例意义
验证租约拥有者可以将线程推进到 `done` 终态,并生成结果消息。
### 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
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."
```
### 预期输出
- 命令退出码为 `0`
- `thread.status == "done"`
- `message.kind == "result"`
### 断言结论
- `done` 会把线程推进到成功终态
- 完成时会释放活跃 lease
## case: done-persists-result-body-and-artifact
### 用例意义
验证 `done` 能持久化结果正文与附件,并被后续 `show` 读取。
### 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
- `TMPDIR/result.md` 已存在
### 输入
```bash
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Retry policy implemented" --body-file TMPDIR/result.md --artifact TMPDIR/result.md --artifact-kind report
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `done` 成功
- 最终结果消息 `body` 等于文件内容
- 结果消息包含 1 个 `report` artifact
### 断言结论
- `done` 是结果交付命令,不只是状态切换命令
## case: done-rejects-non-owner
### 用例意义
验证非租约拥有者不能代替执行者完成线程。
### 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json done --agent worker-b --thread THREAD_ID --summary "Retry policy implemented"
```
### 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
### 断言结论
- `done` 受活跃 lease 所属者约束
## case: done-rejects-on-terminal-thread
### 用例意义
验证已进入终态的线程不能再次执行 `done`
### 前置条件
- 线程 `THREAD_ID` 已经是 `done``failed``cancelled`
### 输入
```bash
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Retry policy implemented"
```
### 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_state`
### 断言结论
- `done` 对终态线程是幂等失败,而不是重复成功
@@ -0,0 +1,33 @@
# Case: `done-marks-thread-terminal`
## 用例意义
验证租约拥有者可以将线程推进到 `done` 终态,并生成结果消息。
## 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
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."
```
## 预期输出
- 命令退出码为 `0`
- `thread.status == "done"`
- `message.kind == "result"`
## 断言结论
- `done` 会把线程推进到成功终态
- 完成时会释放活跃 lease
## 补充约束
-`--agent` 未显式提供时,可以回退使用根级 `--agent`
- 若线程存在但当前没有活跃 lease,例如 lease 已释放或过期,`done` 应返回 `invalid_state`,而不是 `lease_conflict`
- `--thread``--summary` 是必填 flag;缺失时属于 `invalid_input` 类 usage error
@@ -0,0 +1,34 @@
# Case: `done-persists-result-body-and-artifact`
## 用例意义
验证 `done` 能持久化结果正文与附件,并被后续 `show` 读取。
## 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
- `TMPDIR/result.md` 已存在
## 输入
```bash
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Retry policy implemented" --body-file TMPDIR/result.md --artifact TMPDIR/result.md --artifact-kind report
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
## 预期输出
- `done` 成功
- 最终结果消息 `body` 等于文件内容
- 结果消息包含 1 个 `report` artifact
## 断言结论
- `done` 是结果交付命令,不只是状态切换命令
- `done` 也支持 `--payload-json`;若传入非法 JSON,应返回 `invalid_input`
## 补充约束
- `--body``--body-file` 互斥;不可读的 `--body-file` 也属于 `invalid_input`
- artifact 相关 flag 依赖至少一个 `--artifact`,并遵守“指定一次或按 artifact 数量逐个指定”的计数规则
@@ -0,0 +1,25 @@
# Case: `done-rejects-non-owner`
## 用例意义
验证非租约拥有者不能代替执行者完成线程。
## 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json done --agent worker-b --thread THREAD_ID --summary "Retry policy implemented"
```
## 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
## 断言结论
- `done` 受活跃 lease 所属者约束
@@ -0,0 +1,25 @@
# Case: `done-rejects-on-terminal-thread`
## 用例意义
验证已进入终态的线程不能再次执行 `done`
## 前置条件
- 线程 `THREAD_ID` 已经是 `done``failed``cancelled`
## 输入
```bash
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Retry policy implemented"
```
## 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_state`
## 断言结论
- `done` 对终态线程是幂等失败,而不是重复成功
+8 -109
View File
@@ -1,111 +1,10 @@
# Inbox `fail` Test Plan # Inbox `fail` Test Plan Index
## Scope ## Case Files
This document covers failure terminal completion via `inbox fail`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `fail-marks-thread-failed` | [fail-marks-thread-failed.md](./fail-marks-thread-failed.md) | marks a claimed thread as `failed` with a result message |
| `fail-persists-failure-body-and-artifact` | [fail-persists-failure-body-and-artifact.md](./fail-persists-failure-body-and-artifact.md) | persists failure body and artifacts for diagnosis |
## case: fail-marks-thread-failed | `fail-rejects-non-owner` | [fail-rejects-non-owner.md](./fail-rejects-non-owner.md) | rejects `fail` from non-owner agent |
| `fail-rejects-on-terminal-thread` | [fail-rejects-on-terminal-thread.md](./fail-rejects-on-terminal-thread.md) | rejects `fail` on terminal thread states |
### 用例意义
验证租约拥有者可以把线程推进到 `failed` 终态,并生成失败结果消息。
### 前置条件
- `worker-b` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```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."
```
### 预期输出
- 命令退出码为 `0`
- `thread.status == "failed"`
- `message.kind == "result"`
### 断言结论
- `fail``done` 共享结果消息模型,但进入的是失败终态
## case: fail-persists-failure-body-and-artifact
### 用例意义
验证 `fail` 能持久化失败说明与附件。
### 前置条件
- `worker-b` 已成功 `claim` 线程 `THREAD_ID`
- `TMPDIR/failure.md` 已存在
### 输入
```bash
inbox --db TMPDIR/coord.db --json fail --agent worker-b --thread THREAD_ID --summary "Migration failed" --body-file TMPDIR/failure.md --artifact TMPDIR/failure.md --artifact-kind report
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `fail` 成功
- 最终结果消息 `body` 等于文件内容
- 结果消息包含 1 个 `report` artifact
### 断言结论
- 失败终态同样要能完整交付排障材料
## case: fail-rejects-non-owner
### 用例意义
验证非租约拥有者不能把线程标记为失败。
### 前置条件
- `worker-b` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json fail --agent worker-x --thread THREAD_ID --summary "Migration failed"
```
### 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
### 断言结论
- `fail``done` 一样受 lease owner 约束
## case: fail-rejects-on-terminal-thread
### 用例意义
验证已进入终态的线程不能再次执行 `fail`
### 前置条件
- 线程 `THREAD_ID` 已经是 `done``failed``cancelled`
### 输入
```bash
inbox --db TMPDIR/coord.db --json fail --agent worker-b --thread THREAD_ID --summary "Migration failed"
```
### 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_state`
### 断言结论
- `fail` 对终态线程不会重复成功
@@ -0,0 +1,33 @@
# Case: `fail-marks-thread-failed`
## 用例意义
验证租约拥有者可以把线程推进到 `failed` 终态,并生成失败结果消息。
## 前置条件
- `worker-b` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```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."
```
## 预期输出
- 命令退出码为 `0`
- `thread.status == "failed"`
- `message.kind == "result"`
## 断言结论
- `fail``done` 共享结果消息模型,但进入的是失败终态
- 成功 `fail` 后会释放当前活跃 lease,避免线程停留在失败终态却仍显示被占用
## 补充约束
-`--agent` 未显式提供时,可以回退使用根级 `--agent`
- `fail` 生成的 `result` 消息会发回线程创建者,而不是发给当前执行者自己
- 如果线程没有活跃 lease`fail` 应返回 `invalid_state`,而不是 `lease_conflict`
@@ -0,0 +1,34 @@
# Case: `fail-persists-failure-body-and-artifact`
## 用例意义
验证 `fail` 能持久化失败说明与附件。
## 前置条件
- `worker-b` 已成功 `claim` 线程 `THREAD_ID`
- `TMPDIR/failure.md` 已存在
## 输入
```bash
inbox --db TMPDIR/coord.db --json fail --agent worker-b --thread THREAD_ID --summary "Migration failed" --body-file TMPDIR/failure.md --artifact TMPDIR/failure.md --artifact-kind report
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
## 预期输出
- `fail` 成功
- 最终结果消息 `body` 等于文件内容
- 结果消息包含 1 个 `report` artifact
## 断言结论
- 失败终态同样要能完整交付排障材料
## 补充约束
- `--payload-json` 需要是合法 JSON;空值会按 `{}` 处理
- `--body``--body-file` 互斥;不可读的 `--body-file` 属于 `invalid_input`
- `artifact-kind``artifact-metadata-json` 不能脱离 `--artifact` 单独使用,且多值数量必须满足“一次全量应用”或“逐 artifact 对齐”
@@ -0,0 +1,25 @@
# Case: `fail-rejects-non-owner`
## 用例意义
验证非租约拥有者不能把线程标记为失败。
## 前置条件
- `worker-b` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json fail --agent worker-x --thread THREAD_ID --summary "Migration failed"
```
## 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
## 断言结论
- `fail``done` 一样受 lease owner 约束
@@ -0,0 +1,25 @@
# Case: `fail-rejects-on-terminal-thread`
## 用例意义
验证已进入终态的线程不能再次执行 `fail`
## 前置条件
- 线程 `THREAD_ID` 已经是 `done``failed``cancelled`
## 输入
```bash
inbox --db TMPDIR/coord.db --json fail --agent worker-b --thread THREAD_ID --summary "Migration failed"
```
## 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_state`
## 断言结论
- `fail` 对终态线程不会重复成功
+8 -115
View File
@@ -1,117 +1,10 @@
# Inbox `fetch` Test Plan # Inbox `fetch` Test Plan Index
## Scope ## Case Files
This document covers agent-scoped candidate thread retrieval via `inbox fetch`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `fetch-returns-pending-thread-for-target-agent` | [fetch-returns-pending-thread-for-target-agent.md](./fetch-returns-pending-thread-for-target-agent.md) | returns pending candidate work for the target agent |
| `fetch-respects-status-and-limit-filters` | [fetch-respects-status-and-limit-filters.md](./fetch-respects-status-and-limit-filters.md) | enforces status filtering and max row count |
## case: fetch-returns-pending-thread-for-target-agent | `fetch-unread-uses-read-cursor` | [fetch-unread-uses-read-cursor.md](./fetch-unread-uses-read-cursor.md) | unread filtering depends on per-agent read cursor state |
| `fetch-returns-no-matching-work-when-empty` | [fetch-returns-no-matching-work-when-empty.md](./fetch-returns-no-matching-work-when-empty.md) | empty fetch result returns no_matching_work |
### 用例意义
验证 `fetch` 能按目标执行者拉取待处理线程。
### 前置条件
- `leader` 已向 `worker-a` 发送至少一个 `pending` 线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json fetch --agent worker-a --status pending
```
### 预期输出
- 命令退出码为 `0`
- 返回 `data.threads`
- 至少包含一个 `assigned_to == "worker-a"``status == "pending"` 的线程
### 断言结论
- `fetch` 默认是执行者视角的候选工作列表,不是全局线程扫描
## case: fetch-respects-status-and-limit-filters
### 用例意义
验证 `fetch` 同时遵守状态过滤与返回上限。
### 前置条件
- `worker-a` 拥有多个不同状态的线程
- 其中至少两个线程满足目标状态过滤条件
### 输入
```bash
inbox --db TMPDIR/coord.db --json fetch --agent worker-a --status pending,blocked --limit 1
```
### 预期输出
- 命令退出码为 `0`
- 返回线程数不超过 `1`
- 返回的每条线程都满足 `status in ["pending","blocked"]`
### 断言结论
- `fetch``status``limit` 会同时生效
- 返回顺序按 `updated_at` 倒序,优先暴露最新线程
## case: fetch-unread-uses-read-cursor
### 用例意义
验证 `fetch --unread` 基于 agent 的 read cursor 计算未读,而不是仅按线程是否存在新消息。
### 前置条件
- `leader` 已向 `worker-e` 发送一个 `pending` 线程 `THREAD_ID`
### 输入
```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` 后,第二次 `fetch --unread` 无匹配结果
- 新消息追加后,第三次 `fetch --unread` 再次返回该线程
### 断言结论
- 未读判断依赖 `thread_reads.last_read_message_id`
- 新消息到达会让同线程重新进入未读结果集
## case: fetch-returns-no-matching-work-when-empty
### 用例意义
验证 `fetch` 在没有匹配线程时返回稳定的“无工作”错误契约。
### 前置条件
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json fetch --agent worker-z --status pending
```
### 预期输出
- 退出码为 `10`
- JSON 错误码为 `no_matching_work`
### 断言结论
- 空结果不是成功空数组,而是显式的“无匹配工作”信号
@@ -0,0 +1,31 @@
# Case: `fetch-respects-status-and-limit-filters`
## 用例意义
验证 `fetch` 同时遵守状态过滤与返回上限。
## 前置条件
- `worker-a` 拥有多个不同状态的线程
- 其中至少两个线程满足目标状态过滤条件
## 输入
```bash
inbox --db TMPDIR/coord.db --json fetch --agent worker-a --status pending,blocked --limit 1
```
## 预期输出
- 命令退出码为 `0`
- 返回线程数不超过 `1`
- 返回的每条线程都满足 `status in ["pending","blocked"]`
## 断言结论
- `fetch``status``limit` 会同时生效
- 返回顺序按 `updated_at` 倒序,优先暴露最新线程
## 补充约束
- `--limit` 传入 `0` 或负数时,实际会回退到默认上限 `20`
@@ -0,0 +1,24 @@
# Case: `fetch-returns-no-matching-work-when-empty`
## 用例意义
验证 `fetch` 在没有匹配线程时返回稳定的“无工作”错误契约。
## 前置条件
- 空数据库已完成 `init`
## 输入
```bash
inbox --db TMPDIR/coord.db --json fetch --agent worker-z --status pending
```
## 预期输出
- 退出码为 `10`
- JSON 错误码为 `no_matching_work`
## 断言结论
- 空结果不是成功空数组,而是显式的“无匹配工作”信号
@@ -0,0 +1,30 @@
# Case: `fetch-returns-pending-thread-for-target-agent`
## 用例意义
验证 `fetch` 能按目标执行者拉取待处理线程。
## 前置条件
- `leader` 已向 `worker-a` 发送至少一个 `pending` 线程
## 输入
```bash
inbox --db TMPDIR/coord.db --json fetch --agent worker-a --status pending
```
## 预期输出
- 命令退出码为 `0`
- 返回 `data.threads`
- 至少包含一个 `assigned_to == "worker-a"``status == "pending"` 的线程
## 断言结论
- `fetch` 默认是执行者视角的候选工作列表,不是全局线程扫描
## 补充约束
- 未显式传 `--status` 时,`fetch` 默认只查询 `pending` 线程
- 未显式传命令级 `--agent` 时,可回退到根级 `--agent`
@@ -0,0 +1,34 @@
# Case: `fetch-unread-uses-read-cursor`
## 用例意义
验证 `fetch --unread` 基于 agent 的 read cursor 计算未读,而不是仅按线程是否存在新消息。
## 前置条件
- `leader` 已向 `worker-e` 发送一个 `pending` 线程 `THREAD_ID`
## 输入
```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` 后,第二次 `fetch --unread` 无匹配结果
- 新消息追加后,第三次 `fetch --unread` 再次返回该线程
## 断言结论
- 未读判断依赖 `thread_reads.last_read_message_id`
- 新消息到达会让同线程重新进入未读结果集
## 补充约束
- 使用 `--unread` 时必须具备 agent 身份,否则会返回 `invalid_input`
+6 -62
View File
@@ -1,64 +1,8 @@
# Inbox `init` Test Plan # Inbox `init` Test Plan Index
## Scope ## Case Files
This document covers the externally visible behavior of `inbox init`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `init-creates-schema-on-empty-db` | [init-creates-schema-on-empty-db.md](./init-creates-schema-on-empty-db.md) | initializes an empty database path and returns initialized status |
| `init-is-idempotent-on-existing-db` | [init-is-idempotent-on-existing-db.md](./init-is-idempotent-on-existing-db.md) | repeated init succeeds on the same database path |
## case: init-creates-schema-on-empty-db
### 用例意义
验证在空数据库路径上执行 `init` 会创建可用的 inbox schema,并返回稳定的初始化响应。
### 前置条件
- 选择一个尚不存在的数据库路径 `TMPDIR/coord.db`
### 输入
```bash
inbox --db TMPDIR/coord.db --json init
```
### 预期输出
- 命令退出码为 `0`
- 返回 `ok=true`
- `command``init`
- `data.db_path` 等于传入路径
- `data.status``initialized`
### 断言结论
- `init` 在空路径上可以直接完成 schema 初始化
- 初始化结果足以让后续 `send``fetch` 等命令继续使用同一数据库
## case: init-is-idempotent-on-existing-db
### 用例意义
验证 `init` 可以对已初始化过的数据库重复执行,而不会报错或破坏已有 schema。
### 前置条件
- `TMPDIR/coord.db` 已经执行过一次 `inbox --db TMPDIR/coord.db --json init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json init
inbox --db TMPDIR/coord.db --json init
```
### 预期输出
- 两次命令都退出码为 `0`
- 两次响应都返回 `data.status == "initialized"`
- 两次响应都返回相同的 `data.db_path`
### 断言结论
- `init` 是幂等操作
- 对已存在 schema 的重复初始化不应引入额外迁移失败或状态漂移
@@ -0,0 +1,28 @@
# Case: `init-creates-schema-on-empty-db`
## 用例意义
验证在空数据库路径上执行 `init` 会创建可用的 inbox schema,并返回稳定的初始化响应。
## 前置条件
- 选择一个尚不存在的数据库路径 `TMPDIR/coord.db`
## 输入
```bash
inbox --db TMPDIR/coord.db --json init
```
## 预期输出
- 命令退出码为 `0`
- 返回 `ok=true`
- `command``init`
- `data.db_path` 等于传入路径
- `data.status``initialized`
## 断言结论
- `init` 在空路径上可以直接完成 schema 初始化
- 初始化结果足以让后续 `send``fetch` 等命令继续使用同一数据库
@@ -0,0 +1,27 @@
# Case: `init-is-idempotent-on-existing-db`
## 用例意义
验证 `init` 可以对已初始化过的数据库重复执行,而不会报错或破坏已有 schema。
## 前置条件
- `TMPDIR/coord.db` 已经执行过一次 `inbox --db TMPDIR/coord.db --json init`
## 输入
```bash
inbox --db TMPDIR/coord.db --json init
inbox --db TMPDIR/coord.db --json init
```
## 预期输出
- 两次命令都退出码为 `0`
- 两次响应都返回 `data.status == "initialized"`
- 两次响应都返回相同的 `data.db_path`
## 断言结论
- `init` 是幂等操作
- 对已存在 schema 的重复初始化不应引入额外迁移失败或状态漂移
+8 -105
View File
@@ -1,107 +1,10 @@
# Inbox `list` Test Plan # Inbox `list` Test Plan Index
## Scope ## Case Files
This document covers thread listing behavior via `inbox list`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `list-filters-by-status` | [list-filters-by-status.md](./list-filters-by-status.md) | filters returned threads by status set |
| `list-filters-by-created-by` | [list-filters-by-created-by.md](./list-filters-by-created-by.md) | filters returned threads by creator |
## case: list-filters-by-status | `list-filters-by-assigned-to` | [list-filters-by-assigned-to.md](./list-filters-by-assigned-to.md) | filters returned threads by current assignee |
| `list-respects-limit` | [list-respects-limit.md](./list-respects-limit.md) | enforces hard cap on returned thread count |
### 用例意义
验证 `list --status` 只返回指定状态集合内的线程。
### 前置条件
- 数据库中存在多个不同状态的线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json list --status pending,blocked
```
### 预期输出
- 命令退出码为 `0`
- 返回的每条线程都满足 `status in ["pending","blocked"]`
### 断言结论
- `list` 是全局筛选视角,状态过滤不会被忽略
## case: list-filters-by-created-by
### 用例意义
验证 `list --created-by` 能按线程创建者筛选结果。
### 前置条件
- 至少有两位不同创建者产生的线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json list --created-by leader
```
### 预期输出
- 命令退出码为 `0`
- 返回的每条线程都满足 `created_by == "leader"`
### 断言结论
- `created-by` 过滤条件直接作用在线程元数据上
## case: list-filters-by-assigned-to
### 用例意义
验证 `list --assigned-to` 能按当前指派执行者筛选线程。
### 前置条件
- 数据库中存在多个不同 `assigned_to` 的线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json list --assigned-to worker-d --status pending
```
### 预期输出
- 命令退出码为 `0`
- 返回的每条线程都满足 `assigned_to == "worker-d"`
### 断言结论
- `list` 可用于管理侧查看某位执行者当前承担的线程集合
## case: list-respects-limit
### 用例意义
验证 `list --limit` 会约束返回条数,并按更新时间倒序返回最新线程。
### 前置条件
- 存在多个满足过滤条件的线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json list --assigned-to worker-d --limit 1
```
### 预期输出
- 命令退出码为 `0`
- 返回线程数不超过 `1`
### 断言结论
- `list` 的 limit 是硬上限,不会返回超量结果
@@ -0,0 +1,25 @@
# case: list-filters-by-assigned-to
### 用例意义
验证 `list --assigned-to` 能按当前指派执行者筛选线程。
### 前置条件
- 数据库中存在多个不同 `assigned_to` 的线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json list --assigned-to worker-d --status pending
```
### 预期输出
- 命令退出码为 `0`
- 返回的每条线程都满足 `assigned_to == "worker-d"`
### 断言结论
- `list` 可用于管理侧查看某位执行者当前承担的线程集合
@@ -0,0 +1,26 @@
# case: list-filters-by-created-by
### 用例意义
验证 `list --created-by` 能按线程创建者筛选结果。
### 前置条件
- 至少有两位不同创建者产生的线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json list --created-by leader
```
### 预期输出
- 命令退出码为 `0`
- 返回的每条线程都满足 `created_by == "leader"`
### 断言结论
- `created-by` 过滤条件直接作用在线程元数据上
- 没有任何匹配线程时,`list` 返回退出码 `10` 和错误码 `no_matching_work`,而不是成功空数组
@@ -0,0 +1,26 @@
# case: list-filters-by-status
### 用例意义
验证 `list --status` 只返回指定状态集合内的线程。
### 前置条件
- 数据库中存在多个不同状态的线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json list --status pending,blocked
```
### 预期输出
- 命令退出码为 `0`
- 返回的每条线程都满足 `status in ["pending","blocked"]`
### 断言结论
- `list` 会严格应用状态过滤
- 当未显式传 `--assigned-to` 时,`list` 可以作为全局视角,也可以在提供 `--agent` 或根级 `--agent` 时退化为“按 assigned-to 过滤”的快捷入口
@@ -0,0 +1,26 @@
# case: list-respects-limit
### 用例意义
验证 `list --limit` 会约束返回条数,并按更新时间倒序返回最新线程。
### 前置条件
- 存在多个满足过滤条件的线程
### 输入
```bash
inbox --db TMPDIR/coord.db --json list --assigned-to worker-d --limit 1
```
### 预期输出
- 命令退出码为 `0`
- 返回线程数不超过 `1`
### 断言结论
- `list` 的 limit 是硬上限,不会返回超量结果
- `--limit <= 0` 时会回退到默认值 `20`
+7 -85
View File
@@ -1,87 +1,9 @@
# Inbox `renew` Test Plan # Inbox `renew` Test Plan Index
## Scope ## Case Files
This document covers lease renewal behavior via `inbox renew`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `renew-extends-active-lease` | [renew-extends-active-lease.md](./renew-extends-active-lease.md) | owner renews an active lease and gets a renewal event |
| `renew-rejects-non-owner` | [renew-rejects-non-owner.md](./renew-rejects-non-owner.md) | non-owner renew attempt returns lease_conflict |
## case: renew-extends-active-lease | `renew-rejects-without-active-lease` | [renew-rejects-without-active-lease.md](./renew-rejects-without-active-lease.md) | missing active lease returns invalid_state |
### 用例意义
验证租约拥有者可以对活跃 lease 执行续租,并生成续租事件消息。
### 前置条件
- `worker-c` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json renew --agent worker-c --thread THREAD_ID --lease-seconds 600
```
### 预期输出
- 命令退出码为 `0`
- `thread.status` 保持原状态
- `message.kind == "event"`
- `message.summary == "lease renewed"`
- `message.payload_json.lease_seconds == 600`
### 断言结论
- `renew` 是在原线程上追加续租事件,而不是重新 claim
## case: renew-rejects-non-owner
### 用例意义
验证非租约拥有者不能续租别人的活跃 lease。
### 前置条件
- `worker-c` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json renew --agent worker-x --thread THREAD_ID --lease-seconds 600
```
### 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
### 断言结论
- `renew``claim` 一样受 lease owner 约束
## case: renew-rejects-without-active-lease
### 用例意义
验证线程没有活跃租约时,`renew` 会明确失败。
### 前置条件
- 已存在线程 `THREAD_ID`
- 该线程当前没有活跃 lease
### 输入
```bash
inbox --db TMPDIR/coord.db --json renew --agent worker-c --thread THREAD_ID --lease-seconds 600
```
### 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_state`
### 断言结论
- `renew` 依赖已有活跃租约
- 没有 lease 属于状态错误,不是 not-found
@@ -0,0 +1,33 @@
# Case: `renew-extends-active-lease`
## 用例意义
验证租约拥有者可以对活跃 lease 执行续租,并生成续租事件消息。
## 前置条件
- `worker-c` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json renew --agent worker-c --thread THREAD_ID --lease-seconds 600
```
## 预期输出
- 命令退出码为 `0`
- `thread.status` 保持原状态
- `message.kind == "event"`
- `message.summary == "lease renewed"`
- `message.payload_json.lease_seconds == 600`
- `message.payload_json.lease_token` 存在
## 断言结论
- `renew` 是在原线程上追加续租事件,而不是重新 claim
## 补充约束
- `renew` 需要 agent 身份;可以通过命令级 `--agent` 提供,也可以回退到根级 `--agent`
- `--lease-seconds` 传入 `0` 或负数时,CLI 会按 `900` 秒默认值处理
@@ -0,0 +1,24 @@
# Case: `renew-rejects-non-owner`
## 用例意义
验证非租约拥有者不能续租别人的活跃 lease。
## 前置条件
- `worker-c` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json renew --agent worker-x --thread THREAD_ID --lease-seconds 600
```
## 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
## 断言结论
- `renew``claim` 一样受 lease owner 约束
@@ -0,0 +1,26 @@
# Case: `renew-rejects-without-active-lease`
## 用例意义
验证线程没有活跃租约时,`renew` 会明确失败。
## 前置条件
- 已存在线程 `THREAD_ID`
- 该线程当前没有活跃 lease
## 输入
```bash
inbox --db TMPDIR/coord.db --json renew --agent worker-c --thread THREAD_ID --lease-seconds 600
```
## 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_state`
## 断言结论
- `renew` 依赖已有活跃租约
- 没有 lease 属于状态错误,不是 not-found
+8 -109
View File
@@ -1,111 +1,10 @@
# Inbox `reply` Test Plan # Inbox `reply` Test Plan Index
## Scope ## Case Files
This document covers reply behavior within an existing thread via `inbox reply`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `reply-adds-answer-message` | [reply-adds-answer-message.md](./reply-adds-answer-message.md) | appends default `answer` message to an existing non-terminal thread |
| `reply-supports-control-kind` | [reply-supports-control-kind.md](./reply-supports-control-kind.md) | supports explicit `--kind control` reply message |
## case: reply-adds-answer-message | `reply-attaches-artifact` | [reply-attaches-artifact.md](./reply-attaches-artifact.md) | appends reply message with artifact payload |
| `reply-rejects-invalid-payload-json` | [reply-rejects-invalid-payload-json.md](./reply-rejects-invalid-payload-json.md) | rejects malformed `--payload-json` input |
### 用例意义
验证 `reply` 默认会向现有线程追加一条 `answer` 消息,并保持线程状态不变。
### 前置条件
- 已存在一个非终态线程 `THREAD_ID`
### 输入
```bash
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."
```
### 预期输出
- 命令退出码为 `0`
- `message.kind == "answer"`
- `thread.thread_id == THREAD_ID`
- 线程状态保持原值
### 断言结论
- `reply` 是线程内追加消息,而不是状态转换命令
## case: reply-supports-control-kind
### 用例意义
验证 `reply --kind control` 可以发送控制类消息,而不局限于默认 `answer`
### 前置条件
- 已存在一个非终态线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-a --thread THREAD_ID --kind control --summary "Pause rollout" --body "Pause rollout until QA confirms the fix."
```
### 预期输出
- 命令退出码为 `0`
- `message.kind == "control"`
### 断言结论
- `reply` 的消息种类可由调用方显式指定
## case: reply-attaches-artifact
### 用例意义
验证 `reply` 支持追加带附件的答复消息。
### 前置条件
- 已存在一个非终态线程 `THREAD_ID`
- `TMPDIR/decision.md` 已存在
### 输入
```bash
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-a --thread THREAD_ID --summary "Retry read timeouts" --artifact TMPDIR/decision.md --artifact-kind brief --artifact-metadata-json '{"label":"decision"}'
```
### 预期输出
- 命令退出码为 `0`
- `message.artifacts` 长度为 `1`
- artifact 路径、kind、metadata 都可读
### 断言结论
- `reply``send/update/done/fail` 共享附件写入契约
## case: reply-rejects-invalid-payload-json
### 用例意义
验证 `reply` 对非法 `--payload-json` 输入返回稳定错误契约。
### 前置条件
- 已存在一个非终态线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-a --thread THREAD_ID --summary "Retry read timeouts" --payload-json not-json
```
### 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_input`
### 断言结论
- `reply` 的 payload 与其他消息写入命令一样需要通过 JSON 校验
@@ -0,0 +1,34 @@
# Case: `reply-adds-answer-message`
## 用例意义
验证 `reply` 默认会向现有线程追加一条 `answer` 消息,并保持线程状态不变。
## 前置条件
- 已存在一个非终态线程 `THREAD_ID`
## 输入
```bash
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."
```
## 预期输出
- 命令退出码为 `0`
- `message.kind == "answer"`
- `thread.thread_id == THREAD_ID`
- 线程状态保持原值
## 断言结论
- `reply` 是线程内追加消息,而不是状态转换命令
## 补充约束
- `--from` 未显式提供时,可以回退使用根级 `--agent`;如果两者都缺失,应返回 `invalid_input`
- `--thread``--to``--summary` 都是必填 flag;缺失时属于 `invalid_input` 类 usage error
- `reply` 只允许作用在既有非终态线程上;缺失线程应返回 `not_found`,终态线程应返回 `invalid_state`
- `--body``--body-file` 互斥;不可读的 `--body-file` 应返回 `invalid_input`
@@ -0,0 +1,31 @@
# Case: `reply-attaches-artifact`
## 用例意义
验证 `reply` 支持追加带附件的答复消息。
## 前置条件
- 已存在一个非终态线程 `THREAD_ID`
- `TMPDIR/decision.md` 已存在
## 输入
```bash
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-a --thread THREAD_ID --summary "Retry read timeouts" --artifact TMPDIR/decision.md --artifact-kind brief --artifact-metadata-json '{"label":"decision"}'
```
## 预期输出
- 命令退出码为 `0`
- `message.artifacts` 长度为 `1`
- artifact 路径、kind、metadata 都可读
## 断言结论
- `reply``send/update/done/fail` 共享附件写入契约
## 补充约束
- `artifact-kind``artifact-metadata-json` 依赖至少一个 `--artifact`;数量不匹配也应返回 `invalid_input`
@@ -0,0 +1,25 @@
# Case: `reply-rejects-invalid-payload-json`
## 用例意义
验证 `reply` 对非法 `--payload-json` 输入返回稳定错误契约。
## 前置条件
- 已存在一个非终态线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-a --thread THREAD_ID --summary "Retry read timeouts" --payload-json not-json
```
## 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_input`
## 断言结论
- `reply` 的 payload 与其他消息写入命令一样需要通过 JSON 校验
@@ -0,0 +1,25 @@
# Case: `reply-supports-control-kind`
## 用例意义
验证 `reply --kind control` 可以发送控制类消息,而不局限于默认 `answer`
## 前置条件
- 已存在一个非终态线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-a --thread THREAD_ID --kind control --summary "Pause rollout" --body "Pause rollout until QA confirms the fix."
```
## 预期输出
- 命令退出码为 `0`
- `message.kind == "control"`
## 断言结论
- `reply` 的消息种类可由调用方显式指定
+12 -176
View File
@@ -1,176 +1,12 @@
# Inbox `send` Test Plan # Inbox `send` Test Plan Index
## Scope ## Case Files
This document covers thread creation and message append behavior exposed by `inbox send`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `send-creates-new-thread` | [send-creates-new-thread.md](./send-creates-new-thread.md) | creates a pending thread with an initial task message |
| `send-appends-message-to-existing-thread` | [send-appends-message-to-existing-thread.md](./send-appends-message-to-existing-thread.md) | appends a message to an existing non-terminal thread |
## case: send-creates-new-thread | `send-reads-body-from-body-file` | [send-reads-body-from-body-file.md](./send-reads-body-from-body-file.md) | reads message body from a file path |
| `send-attaches-artifact-with-metadata` | [send-attaches-artifact-with-metadata.md](./send-attaches-artifact-with-metadata.md) | persists artifact path, kind, and metadata on send |
### 用例意义 | `send-rejects-invalid-payload-json` | [send-rejects-invalid-payload-json.md](./send-rejects-invalid-payload-json.md) | rejects malformed payload JSON with `invalid_input` |
| `send-rejects-invalid-artifact-metadata-json` | [send-rejects-invalid-artifact-metadata-json.md](./send-rejects-invalid-artifact-metadata-json.md) | rejects malformed artifact metadata JSON |
验证 `send` 在未指定既有线程时会创建新线程,并写入首条任务消息。
### 前置条件
- 空数据库已完成 `init`
### 输入
```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
```
### 预期输出
- 命令退出码为 `0`
- 返回 `thread.thread_id`
- `thread.status == "pending"`
- `thread.created_by == "leader"`
- `thread.assigned_to == "worker-a"`
- `message.kind == "task"`
### 断言结论
- `send` 会新建线程而不是只插入孤立消息
- 新线程的默认初始状态是 `pending`
## case: send-appends-message-to-existing-thread
### 用例意义
验证 `send` 在指定既有 `--thread` 时会向原线程追加消息,而不是重建线程。
### 前置条件
- 已存在一个由 `leader` 发给 `worker-d` 的线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-d --thread THREAD_ID --summary "Use a markdown editor" --body "Prefer a textarea-based markdown editor for v1."
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `send` 成功,返回的 `thread.thread_id` 仍为 `THREAD_ID`
- 线程状态保持原值,不被强制改写为新状态
- `show` 可见消息数增加
### 断言结论
- 追加消息不会重置线程生命周期
- 线程历史按时间顺序保留旧消息与新消息
## case: send-reads-body-from-body-file
### 用例意义
验证 `send --body-file` 会把文件内容写入消息正文。
### 前置条件
- `TMPDIR/task.md` 已存在,内容为测试正文
### 输入
```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
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `send` 成功
- `show` 首条消息的 `body` 与文件内容一致
### 断言结论
- `body-file` 内容会被原样读取
- 该行为与直接传 `--body` 的最终存储结果等价
## case: send-attaches-artifact-with-metadata
### 用例意义
验证 `send` 支持附带 artifact、kind 和 metadata,并可在返回值或后续 `show` 中读取。
### 前置条件
- `TMPDIR/task.md` 已存在
### 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-d --subject "Build admin editor" --summary "Create the first editor screen" --artifact TMPDIR/task.md --artifact-kind brief --artifact-metadata-json '{"label":"task-brief"}'
```
### 预期输出
- 命令退出码为 `0`
- `message.artifacts` 长度为 `1`
- artifact `path == "TMPDIR/task.md"`
- artifact `kind == "brief"`
- artifact `metadata_json.label == "task-brief"`
### 断言结论
- `send` 可以在创建消息时持久化附件及其结构化元数据
## case: send-rejects-invalid-payload-json
### 用例意义
验证 `send` 对非法 `--payload-json` 输入给出稳定错误契约。
### 前置条件
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-z --subject "Invalid payload json" --payload-json not-json
```
### 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_input`
### 断言结论
- 非法 payload 在写库前就会被拒绝
- 错误归类为输入问题,而不是内部错误
## case: send-rejects-invalid-artifact-metadata-json
### 用例意义
验证 `send` 对非法 artifact metadata JSON 给出稳定错误契约。
### 前置条件
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-z --subject "Invalid artifact json" --artifact TMPDIR/report.md --artifact-metadata-json not-json
```
### 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_input`
### 断言结论
- artifact metadata 会在写入前校验 JSON 合法性
## Notes
- 新建线程时未显式传 `--summary`,会回退到 `--subject`
- `--body``--body-file` 互斥;该约束由 shared 文档统一说明
@@ -0,0 +1,33 @@
# Case: `send-appends-message-to-existing-thread`
## 用例意义
验证 `send` 在指定既有 `--thread` 时会向原线程追加消息,而不是重建线程。
## 前置条件
- 已存在一个由 `leader` 发给 `worker-d` 的非终态线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-d --thread THREAD_ID --summary "Use a markdown editor" --body "Prefer a textarea-based markdown editor for v1."
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
## 预期输出
- `send` 成功,返回的 `thread.thread_id` 仍为 `THREAD_ID`
- 线程状态保持原值,不被强制改写为新状态
- `show` 可见消息数增加
## 断言结论
- 追加消息不会重置线程生命周期
- 线程历史按时间顺序保留旧消息与新消息
## 补充约束
- `--to` 是 CLI 必填参数;即使是向既有线程追加消息也不能省略
- 对既有线程执行追加时,如果传入了不同的 `--to`,线程的 `assigned_to` 会更新为新的接收者
- 终态线程不允许继续通过 `send` 追加消息,预期错误类型为 `invalid_state`
@@ -0,0 +1,27 @@
# Case: `send-attaches-artifact-with-metadata`
## 用例意义
验证 `send` 支持附带 artifact、kind 和 metadata,并可在返回值或后续 `show` 中读取。
## 前置条件
- `TMPDIR/task.md` 已存在
## 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-d --subject "Build admin editor" --summary "Create the first editor screen" --artifact TMPDIR/task.md --artifact-kind brief --artifact-metadata-json '{"label":"task-brief"}'
```
## 预期输出
- 命令退出码为 `0`
- `message.artifacts` 长度为 `1`
- artifact `path == "TMPDIR/task.md"`
- artifact `kind == "brief"`
- artifact `metadata_json.label == "task-brief"`
## 断言结论
- `send` 可以在创建消息时持久化附件及其结构化元数据
@@ -0,0 +1,36 @@
# Case: `send-creates-new-thread`
## 用例意义
验证 `send` 在未指定既有线程时会创建新线程,并写入首条任务消息。
## 前置条件
- 空数据库已完成 `init`
## 输入
```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
```
## 预期输出
- 命令退出码为 `0`
- 返回 `thread.thread_id`
- `thread.status == "pending"`
- `thread.created_by == "leader"`
- `thread.assigned_to == "worker-a"`
- `message.kind == "task"`
## 断言结论
- `send` 会新建线程而不是只插入孤立消息
- 新线程的默认初始状态是 `pending`
## 补充约束
- `--from` 未显式传入时,会回退使用根级 `--agent`
- 新建线程时未显式传 `--summary`,会回退到 `--subject`
- 新建线程时 `--kind` 默认是 `task``--priority` 默认是 `normal`
-`--thread` 指向不存在的线程时,`send` 会使用该 thread ID 新建线程,而不是返回 `not_found`
@@ -0,0 +1,30 @@
# Case: `send-reads-body-from-body-file`
## 用例意义
验证 `send --body-file` 会把文件内容写入消息正文。
## 前置条件
- `TMPDIR/task.md` 已存在,内容为测试正文
## 输入
```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
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
## 预期输出
- `send` 成功
- `show` 首条消息的 `body` 与文件内容一致
## 断言结论
- `body-file` 内容会被原样读取
- 该行为与直接传 `--body` 的最终存储结果等价
## 补充约束
- `--body``--body-file` 互斥;该约束由 shared 文档统一说明
@@ -0,0 +1,24 @@
# Case: `send-rejects-invalid-artifact-metadata-json`
## 用例意义
验证 `send` 对非法 artifact metadata JSON 给出稳定错误契约。
## 前置条件
- 空数据库已完成 `init`
## 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-z --subject "Invalid artifact json" --artifact TMPDIR/report.md --artifact-metadata-json not-json
```
## 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_input`
## 断言结论
- artifact metadata 会在写入前校验 JSON 合法性
@@ -0,0 +1,25 @@
# Case: `send-rejects-invalid-payload-json`
## 用例意义
验证 `send` 对非法 `--payload-json` 输入给出稳定错误契约。
## 前置条件
- 空数据库已完成 `init`
## 输入
```bash
inbox --db TMPDIR/coord.db --json send --from leader --to worker-z --subject "Invalid payload json" --payload-json not-json
```
## 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_input`
## 断言结论
- 非法 payload 在写库前就会被拒绝
- 错误归类为输入问题,而不是内部错误
+8 -113
View File
@@ -1,115 +1,10 @@
# Inbox `show` Test Plan # Inbox `show` Test Plan Index
## Scope ## Case Files
This document covers per-thread detail retrieval via `inbox show`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `show-returns-thread-and-message-history` | [show-returns-thread-and-message-history.md](./show-returns-thread-and-message-history.md) | returns thread details and full time-ordered message history |
| `show-includes-artifacts-per-message` | [show-includes-artifacts-per-message.md](./show-includes-artifacts-per-message.md) | expands per-message artifacts in the show payload |
## case: show-returns-thread-and-message-history | `show-mark-read-advances-read-cursor` | [show-mark-read-advances-read-cursor.md](./show-mark-read-advances-read-cursor.md) | advances caller read cursor when `--mark-read` is used |
| `show-rejects-when-thread-missing` | [show-rejects-when-thread-missing.md](./show-rejects-when-thread-missing.md) | returns stable not-found contract for missing thread |
### 用例意义
验证 `show` 会返回线程详情和完整消息历史。
### 前置条件
- 已存在一个含多条消息的线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- 命令退出码为 `0`
- 返回 `data.thread`
- 返回 `data.messages`
- 消息按创建时间升序排列
### 断言结论
- `show` 是线程详情与时间序历史的读取入口
## case: show-includes-artifacts-per-message
### 用例意义
验证 `show` 返回的每条消息都包含其关联 artifact 列表。
### 前置条件
- 线程 `THREAD_ID` 中至少一条消息附带 artifact
### 输入
```bash
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- 命令退出码为 `0`
- 相关消息节点包含 `artifacts`
- artifact 的 `path``kind``metadata_json` 可读
### 断言结论
- `show` 需要把附件一并展开,而不是只返回 message 基本字段
## case: show-mark-read-advances-read-cursor
### 用例意义
验证 `show --mark-read` 会推进调用 agent 的 read cursor,并影响后续 unread 查询。
### 前置条件
- `worker-e` 有一个未读线程 `THREAD_ID`
### 输入
```bash
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
```
### 预期输出
- `show` 成功
- 随后的 `fetch --unread` 对该线程不再返回结果
### 断言结论
- `mark-read` 的副作用是推进该 agent 的 `last_read_message_id`
## case: show-rejects-when-thread-missing
### 用例意义
验证 `show` 对不存在线程返回稳定的 not-found 错误契约。
### 前置条件
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json show --thread thr_missing
```
### 预期输出
- 退出码为 `40`
- JSON 错误码为 `not_found`
### 断言结论
- `show` 不会对缺失线程返回空对象
## Notes
- 使用 `--mark-read` 时必须提供 agent 身份,可通过根级 `--agent` 或命令参数传入
@@ -0,0 +1,26 @@
# case: show-includes-artifacts-per-message
### 用例意义
验证 `show` 返回的每条消息都包含其关联 artifact 列表。
### 前置条件
- 线程 `THREAD_ID` 中至少一条消息附带 artifact
### 输入
```bash
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- 命令退出码为 `0`
- 相关消息节点包含 `artifacts`
- artifact 的 `path``kind``metadata_json` 可读
### 断言结论
- `show` 需要把附件一并展开,而不是只返回 message 基本字段
@@ -0,0 +1,27 @@
# case: show-mark-read-advances-read-cursor
### 用例意义
验证 `show --mark-read` 会推进调用 agent 的 read cursor,并影响后续 unread 查询。
### 前置条件
- `worker-e` 有一个未读线程 `THREAD_ID`
### 输入
```bash
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
```
### 预期输出
- `show` 成功
- 随后的 `fetch --unread` 对该线程不再返回结果
### 断言结论
- `mark-read` 的副作用是推进该 agent 的 `last_read_message_id`
- 使用 `--mark-read` 时必须提供 agent 身份,可通过根级 `--agent` 或命令参数传入
@@ -0,0 +1,26 @@
# case: show-rejects-when-thread-missing
### 用例意义
验证 `show` 对不存在线程返回稳定的 not-found 错误契约。
### 前置条件
- 空数据库已完成 `init`
### 输入
```bash
inbox --db TMPDIR/coord.db --json show --thread thr_missing
```
### 预期输出
- 退出码为 `40`
- JSON 错误码为 `not_found`
### 断言结论
- `show` 不会对缺失线程返回空对象
- `--thread` 是必填 flag;缺失时属于 `invalid_input` 类 usage error
@@ -0,0 +1,29 @@
# case: show-returns-thread-and-message-history
### 用例意义
验证 `show` 会返回线程详情和完整消息历史。
### 前置条件
- 已存在一个含多条消息的线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- 命令退出码为 `0`
- 返回 `data.thread`
- 返回 `data.messages`
- 消息按创建时间升序排列
### 断言结论
- `show` 是线程详情与时间序历史的读取入口
- `show` 不依赖线程是否处于活动态;只要线程存在,就应能读取包括终态线程在内的完整历史
- 未使用 `--mark-read` 时,`show` 不要求提供 agent 身份
+9 -137
View File
@@ -1,139 +1,11 @@
# Inbox `update` Test Plan # Inbox `update` Test Plan Index
## Scope ## Case Files
This document covers progress and blocked transitions via `inbox update`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `update-moves-thread-to-in-progress` | [update-moves-thread-to-in-progress.md](./update-moves-thread-to-in-progress.md) | moves a claimed thread to `in_progress` and emits a progress message |
| `update-moves-thread-to-blocked-with-payload` | [update-moves-thread-to-blocked-with-payload.md](./update-moves-thread-to-blocked-with-payload.md) | moves a claimed thread to `blocked` with structured question payload |
## case: update-moves-thread-to-in-progress | `update-accepts-body-file-and-artifact` | [update-accepts-body-file-and-artifact.md](./update-accepts-body-file-and-artifact.md) | persists update body from file plus artifacts |
| `update-rejects-invalid-payload-json` | [update-rejects-invalid-payload-json.md](./update-rejects-invalid-payload-json.md) | rejects malformed `--payload-json` input |
### 用例意义 | `update-rejects-non-owner` | [update-rejects-non-owner.md](./update-rejects-non-owner.md) | rejects update when caller is not the active lease owner |
验证租约拥有者可以把线程推进到 `in_progress`,并生成进度消息。
### 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
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."
```
### 预期输出
- 命令退出码为 `0`
- `thread.status == "in_progress"`
- `message.kind == "progress"`
- `message.to_agent` 指向线程创建者
### 断言结论
- `update` 会把状态推进和消息追加合并为同一次事务
## case: update-moves-thread-to-blocked-with-payload
### 用例意义
验证 `update --status blocked` 会写入阻塞问题消息,并保留结构化 payload。
### 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
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?"}'
```
### 预期输出
- 命令退出码为 `0`
- `thread.status == "blocked"`
- `message.kind == "question"`
- `message.payload_json.question` 保存提问内容
### 断言结论
- `blocked` 更新会生成面向创建者的问题消息
## case: update-accepts-body-file-and-artifact
### 用例意义
验证 `update` 支持通过 `body-file` 与 artifact 发送结构化进度材料。
### 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
- `TMPDIR/progress.md` 已存在
### 输入
```bash
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status in_progress --summary "Implementation started" --body-file TMPDIR/progress.md --artifact TMPDIR/progress.md --artifact-kind note
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
### 预期输出
- `update` 成功
- 对应消息 `body` 等于文件内容
- 对应消息包含 1 个 artifactkind 为 `note`
### 断言结论
- `update` 的正文与 artifact 支持与 `send/reply/done/fail` 保持一致
## case: update-rejects-invalid-payload-json
### 用例意义
验证 `update` 对非法 `--payload-json` 输入返回稳定错误契约。
### 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status blocked --summary "Need timeout decision" --payload-json not-json
```
### 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_input`
### 断言结论
- 阻塞问题的 payload 需要满足合法 JSON 约束
## case: update-rejects-non-owner
### 用例意义
验证非租约拥有者不能更新线程状态。
### 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
### 输入
```bash
inbox --db TMPDIR/coord.db --json update --agent worker-b --thread THREAD_ID --status in_progress --summary "Implementation started"
```
### 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
### 断言结论
- `update` 明确依赖活跃 lease 所属者
@@ -0,0 +1,33 @@
# Case: `update-accepts-body-file-and-artifact`
## 用例意义
验证 `update` 支持通过 `body-file` 与 artifact 发送结构化进度材料。
## 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
- `TMPDIR/progress.md` 已存在
## 输入
```bash
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status in_progress --summary "Implementation started" --body-file TMPDIR/progress.md --artifact TMPDIR/progress.md --artifact-kind note
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
```
## 预期输出
- `update` 成功
- 对应消息 `body` 等于文件内容
- 对应消息包含 1 个 artifactkind 为 `note`
## 断言结论
- `update` 的正文与 artifact 支持与 `send/reply/done/fail` 保持一致
## 补充约束
- `--body``--body-file` 互斥;读取 `body-file` 失败时应返回 `invalid_input`
- `artifact-kind``artifact-metadata-json` 不能脱离 `--artifact` 单独使用;数量不匹配时也应返回 `invalid_input`
@@ -0,0 +1,27 @@
# Case: `update-moves-thread-to-blocked-with-payload`
## 用例意义
验证 `update --status blocked` 会写入阻塞问题消息,并保留结构化 payload。
## 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
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?"}'
```
## 预期输出
- 命令退出码为 `0`
- `thread.status == "blocked"`
- `message.kind == "question"`
- `message.payload_json.question` 保存提问内容
## 断言结论
- `blocked` 更新会生成面向创建者的问题消息
@@ -0,0 +1,34 @@
# Case: `update-moves-thread-to-in-progress`
## 用例意义
验证租约拥有者可以把线程推进到 `in_progress`,并生成进度消息。
## 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
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."
```
## 预期输出
- 命令退出码为 `0`
- `thread.status == "in_progress"`
- `message.kind == "progress"`
- `message.to_agent` 指向线程创建者
## 断言结论
- `update` 会把状态推进和消息追加合并为同一次事务
## 补充约束
- `update` 只接受 `in_progress``blocked` 两种 `--status`;其他值应返回退出码 `30` 与错误码 `invalid_input`
- `update` 依赖活跃 lease
- 若线程存在活跃 lease 但归属其他 agent,应返回 `lease_conflict`
- 若线程当前没有活跃 lease,应返回 `invalid_state`
@@ -0,0 +1,25 @@
# Case: `update-rejects-invalid-payload-json`
## 用例意义
验证 `update` 对非法 `--payload-json` 输入返回稳定错误契约。
## 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status blocked --summary "Need timeout decision" --payload-json not-json
```
## 预期输出
- 退出码为 `30`
- JSON 错误码为 `invalid_input`
## 断言结论
- 阻塞问题的 payload 需要满足合法 JSON 约束
@@ -0,0 +1,25 @@
# Case: `update-rejects-non-owner`
## 用例意义
验证非租约拥有者不能更新线程状态。
## 前置条件
- `worker-a` 已成功 `claim` 线程 `THREAD_ID`
## 输入
```bash
inbox --db TMPDIR/coord.db --json update --agent worker-b --thread THREAD_ID --status in_progress --summary "Implementation started"
```
## 预期输出
- 退出码为 `20`
- JSON 错误码为 `lease_conflict`
## 断言结论
- `update` 明确依赖活跃 lease 所属者
+7 -91
View File
@@ -1,93 +1,9 @@
# Inbox `wait-reply` Test Plan # Inbox `wait-reply` Test Plan Index
## Scope ## Case Files
This document covers blocking reply wait behavior within one thread via `inbox wait-reply`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `wait-reply-wakes-on-answer-after-message` | [wait-reply-wakes-on-answer-after-message.md](./wait-reply-wakes-on-answer-after-message.md) | wakes for a qualifying reply after known message boundary |
| `wait-reply-can-start-from-after-event` | [wait-reply-can-start-from-after-event.md](./wait-reply-can-start-from-after-event.md) | resumes waiting from a known event cursor |
## case: wait-reply-wakes-on-answer-after-message | `wait-reply-times-out-when-no-reply` | [wait-reply-times-out-when-no-reply.md](./wait-reply-times-out-when-no-reply.md) | returns timeout contract when no qualifying reply arrives |
### 用例意义
验证 `wait-reply` 可以从某条已知消息之后开始等待,并在答复到达后唤醒。
### 前置条件
- `worker-c` 已拥有一个 `blocked` 线程 `THREAD_ID`
- 阻塞消息的 `message_id``BLOCKED_MESSAGE_ID`
### 输入
```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."
```
### 预期输出
- `wait-reply` 退出码为 `0`
- `wait-reply.data.woke == true`
- 返回的 `message.kind == "answer"`
### 断言结论
- `wait-reply` 可以可靠地从既知消息边界之后等待后续答复
## case: wait-reply-can-start-from-after-event
### 用例意义
验证 `wait-reply --after-event` 支持从既知事件游标之后恢复等待。
### 前置条件
- 已通过先前的 `watch``wait-reply` 结果拿到某个 `NEXT_EVENT_ID`
- 线程 `THREAD_ID` 后续还会收到新的回复类消息
### 输入
```bash
inbox --db TMPDIR/coord.db --agent worker-c --json wait-reply --thread THREAD_ID --after-event NEXT_EVENT_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."
```
### 预期输出
- `wait-reply` 在事件游标之后的新回复出现时被唤醒
- 返回新的 `next_event_id`
### 断言结论
- `after-event` 允许等待逻辑在断点之后继续,而不会重复消费旧回复
## case: wait-reply-times-out-when-no-reply
### 用例意义
验证在超时时间内没有匹配回复出现时,`wait-reply` 返回稳定超时契约。
### 前置条件
- 存在一个线程 `THREAD_ID`
- 不会有新的 `answer/control/result` 消息到达
### 输入
```bash
inbox --db TMPDIR/coord.db --agent worker-c --json wait-reply --thread THREAD_ID --timeout-seconds 1
```
### 预期输出
- 退出码为 `10`
- JSON 错误码为 `no_matching_work`
### 断言结论
- `wait-reply` 超时被视为“没有等到匹配回复”
## Notes
- 默认唤醒 kinds 为 `answer,control,result`
- 当返回消息是发给等待 agent 的外来消息时,`wait-reply` 会顺带推进该 agent 的 read cursor
@@ -0,0 +1,29 @@
# case: wait-reply-can-start-from-after-event
### 用例意义
验证 `wait-reply --after-event` 支持从既知事件游标之后恢复等待。
### 前置条件
- 已通过先前的 `watch``wait-reply` 结果拿到某个 `NEXT_EVENT_ID`
- 线程 `THREAD_ID` 后续还会收到新的回复类消息
### 输入
```bash
inbox --db TMPDIR/coord.db --agent worker-c --json wait-reply --thread THREAD_ID --after-event NEXT_EVENT_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."
```
### 预期输出
- `wait-reply` 在事件游标之后的新回复出现时被唤醒
- 返回新的 `next_event_id`
### 断言结论
- `after-event` 允许等待逻辑在断点之后继续,而不会重复消费旧回复
- `--kinds` 支持自定义逗号分隔的唤醒消息类型;未显式提供时默认使用 `answer,control,result`
- 默认唤醒 kinds 为 `answer,control,result`
@@ -0,0 +1,28 @@
# case: wait-reply-times-out-when-no-reply
### 用例意义
验证在超时时间内没有匹配回复出现时,`wait-reply` 返回稳定超时契约。
### 前置条件
- 存在一个线程 `THREAD_ID`
- 不会有新的 `answer/control/result` 消息到达
### 输入
```bash
inbox --db TMPDIR/coord.db --agent worker-c --json wait-reply --thread THREAD_ID --timeout-seconds 1
```
### 预期输出
- 退出码为 `10`
- JSON 错误码为 `no_matching_work`
### 断言结论
- `wait-reply` 超时被视为“没有等到匹配回复”
- `--thread` 是必填 flag;缺失时属于 `invalid_input` 类 usage error
- `--timeout-seconds=0` 表示无限等待,而不是立即超时
@@ -0,0 +1,31 @@
# case: wait-reply-wakes-on-answer-after-message
### 用例意义
验证 `wait-reply` 可以从某条已知消息之后开始等待,并在答复到达后唤醒。
### 前置条件
- `worker-c` 已拥有一个 `blocked` 线程 `THREAD_ID`
- 阻塞消息的 `message_id``BLOCKED_MESSAGE_ID`
### 输入
```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."
```
### 预期输出
- `wait-reply` 退出码为 `0`
- `wait-reply.data.woke == true`
- 返回的 `message.kind == "answer"`
### 断言结论
- `wait-reply` 可以可靠地从既知消息边界之后等待后续答复
- `--agent` 不是必填;它主要用于在命中外来消息时推进该 agent 的 read cursor
- `--after-message` 必须引用该线程中已知的消息;如果消息不存在,应返回 `not_found`
- 当返回消息是发给等待 agent 的外来消息时,`wait-reply` 会顺带推进该 agent 的 read cursor
+7 -89
View File
@@ -1,91 +1,9 @@
# Inbox `watch` Test Plan # Inbox `watch` Test Plan Index
## Scope ## Case Files
This document covers blocking thread-activity wait behavior via `inbox watch`. | Case Slug | File | Coverage Note |
| --- | --- | --- |
Shared conventions live in [../_shared/README.md](../_shared/README.md). | `watch-wakes-on-matching-thread` | [watch-wakes-on-matching-thread.md](./watch-wakes-on-matching-thread.md) | wakes when a matching post-start event arrives and returns event context |
| `watch-respects-status-filter` | [watch-respects-status-filter.md](./watch-respects-status-filter.md) | wakes only when thread transitions into requested status |
## case: watch-wakes-on-matching-thread | `watch-times-out-with-no-activity` | [watch-times-out-with-no-activity.md](./watch-times-out-with-no-activity.md) | returns timeout contract when no matching activity arrives |
### 用例意义
验证 `watch` 在新匹配线程到达时会被唤醒,并返回线程、消息与事件信息。
### 前置条件
- `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"
```
### 预期输出
- `watch` 退出码为 `0`
- `watch.data.woke == true`
- 返回 `thread``message``event`
### 断言结论
- `watch` 唤醒结果不仅说明“醒了”,还提供触发该唤醒的具体事件上下文
## case: watch-respects-status-filter
### 用例意义
验证 `watch --status` 只会对匹配状态的后续事件唤醒。
### 前置条件
- 存在一个会被推进到 `blocked` 的线程 `THREAD_ID`
- `watch``--status blocked` 先启动
### 输入
```bash
inbox --db TMPDIR/coord.db --json watch --agent worker-c --status blocked --timeout-seconds 2
inbox --db TMPDIR/coord.db --json update --agent worker-c --thread THREAD_ID --status blocked --summary "Need policy decision"
```
### 预期输出
- `watch` 只在线程进入 `blocked` 后返回
- 返回的 `thread.status == "blocked"`
### 断言结论
- `watch` 的状态过滤作用在“事件发生后的线程状态”上
## case: watch-times-out-with-no-activity
### 用例意义
验证在超时时间内没有匹配活动时,`watch` 返回稳定超时契约。
### 前置条件
- 没有新匹配事件会发生
### 输入
```bash
inbox --db TMPDIR/coord.db --json watch --agent worker-d --status pending --timeout-seconds 1
```
### 预期输出
- 退出码为 `10`
- JSON 错误码为 `no_matching_work`
### 断言结论
- `watch` 超时被归类为“无匹配工作”,而不是内部错误
## Notes
- 未传 `--after-event` 时,`watch` 默认从“当前时刻之后”开始等待,不会回放既有事件
@@ -0,0 +1,29 @@
# case: watch-respects-status-filter
### 用例意义
验证 `watch --status` 只会对匹配状态的后续事件唤醒。
### 前置条件
- 存在一个会被推进到 `blocked` 的线程 `THREAD_ID`
- `watch``--status blocked` 先启动
### 输入
```bash
inbox --db TMPDIR/coord.db --json watch --agent worker-c --status blocked --timeout-seconds 2
inbox --db TMPDIR/coord.db --json update --agent worker-c --thread THREAD_ID --status blocked --summary "Need policy decision"
```
### 预期输出
- `watch` 只在线程进入 `blocked` 后返回
- 返回的 `thread.status == "blocked"`
### 断言结论
- `watch` 的状态过滤作用在“事件发生后的线程状态”上
- `--status` 默认值为 `pending,blocked,done,failed`;未显式传入时,`watch` 不是只观察 `pending`
- 显式传入 `--after-event` 时,`watch` 会从该事件游标之后恢复,允许调用方断点续看
@@ -0,0 +1,27 @@
# case: watch-times-out-with-no-activity
### 用例意义
验证在超时时间内没有匹配活动时,`watch` 返回稳定超时契约。
### 前置条件
- 没有新匹配事件会发生
### 输入
```bash
inbox --db TMPDIR/coord.db --json watch --agent worker-d --status pending --timeout-seconds 1
```
### 预期输出
- 退出码为 `10`
- JSON 错误码为 `no_matching_work`
### 断言结论
- `watch` 超时被归类为“无匹配工作”,而不是内部错误
- `--timeout-seconds 0` 表示无限等待,而不是立即超时
- 未传 `--after-event` 时,`watch` 默认从“当前时刻之后”开始等待,不会回放既有事件
@@ -0,0 +1,30 @@
# case: watch-wakes-on-matching-thread
### 用例意义
验证 `watch` 在新匹配线程到达时会被唤醒,并返回线程、消息与事件信息。
### 前置条件
- `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"
```
### 预期输出
- `watch` 退出码为 `0`
- `watch.data.woke == true`
- 返回 `thread``message``event`
### 断言结论
- `watch` 唤醒结果不仅说明“醒了”,还提供触发该唤醒的具体事件上下文
- `--agent` 未显式提供时,可以回退使用根级 `--agent`;如果两者都未提供,则 `watch` 变为不按 `assigned_to` 过滤的全局观察
- 成功唤醒时返回的 `next_event_id` 应等于触发唤醒的 `event.event_id`