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
+9 -8
View File
@@ -9,15 +9,16 @@ It complements automated Go tests. The goal is not to restate implementation det
## Directory Rules
- one folder per command or shared area
- one `README.md` per folder
- no one-file-per-case sprawl
- each folder keeps a `README.md` entrypoint
- 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
- 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
## case: send-rejects-invalid-payload-json
```text
<case-slug>.md
```
## Authoring Principles
@@ -48,7 +49,7 @@ Unless a case says otherwise:
- `README.md`: global conventions and glossary
- `_shared/README.md`: reusable fixtures, JSON assertions, exit codes, payload rules
- `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
@@ -66,5 +67,5 @@ The current best executable reference is [internal/cli/inbox/integration_test.go
When this Markdown plan is expanded:
- 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
+171 -90
View File
@@ -25,12 +25,14 @@ Current state:
- `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
- 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`:
- planned document files: `17`
- authored document files: `17`
- planned document files: `70`
- authored document files: `70`
- planned case slugs in this roadmap: `61`
- authored case slugs in this roadmap: `61`
@@ -66,20 +68,29 @@ Out of scope:
Directory model:
- one folder per command or shared area
- one `README.md` per folder
- no one-file-per-case sprawl
- each folder keeps 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
- cross-command workflow cases remain grouped in `docs/tests/inbox/workflows/README.md`
Case identity:
- do not use numeric IDs
- identify a case by `path + case slug`
- recommended heading pattern inside the command document:
- identify each command case by its concrete file path
- identify each workflow case by `path + case slug`
- command case file naming pattern:
```text
<case-slug>.md
```
- workflow case heading pattern:
```md
## 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:
1. add the case content to the target `README.md`
2. move the case slug from `Pending Case Backlog` to `Authored Case Register`
3. update the authored counts in `Current Snapshot`
4. if a whole command document is created, update `Document Progress`
1. if it is a command case, create or update the target `<case-slug>.md` file under the relevant command folder
2. if it is a command case, add or update the entry in that folder `README.md` index
3. if it is a workflow case, add or update the case inside `docs/tests/inbox/workflows/README.md`
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:
@@ -126,32 +139,46 @@ docs/tests/inbox/
README.md
init/
README.md
<case-slug>.md
send/
README.md
<case-slug>.md
fetch/
README.md
<case-slug>.md
claim/
README.md
<case-slug>.md
renew/
README.md
<case-slug>.md
update/
README.md
<case-slug>.md
reply/
README.md
<case-slug>.md
done/
README.md
<case-slug>.md
fail/
README.md
<case-slug>.md
cancel/
README.md
<case-slug>.md
list/
README.md
<case-slug>.md
show/
README.md
<case-slug>.md
watch/
README.md
<case-slug>.md
wait-reply/
README.md
<case-slug>.md
```
## 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/_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/init/README.md` | `init` command cases | 2 | 2 | done |
| `docs/tests/inbox/send/README.md` | `send` command cases | 6 | 6 | done |
| `docs/tests/inbox/fetch/README.md` | `fetch` command cases | 4 | 4 | done |
| `docs/tests/inbox/claim/README.md` | `claim` command cases | 4 | 4 | done |
| `docs/tests/inbox/renew/README.md` | `renew` command cases | 3 | 3 | done |
| `docs/tests/inbox/update/README.md` | `update` command cases | 5 | 5 | done |
| `docs/tests/inbox/reply/README.md` | `reply` command cases | 4 | 4 | done |
| `docs/tests/inbox/done/README.md` | `done` command cases | 4 | 4 | done |
| `docs/tests/inbox/fail/README.md` | `fail` command cases | 4 | 4 | done |
| `docs/tests/inbox/cancel/README.md` | `cancel` command cases | 3 | 3 | done |
| `docs/tests/inbox/list/README.md` | `list` command cases | 4 | 4 | done |
| `docs/tests/inbox/show/README.md` | `show` command cases | 4 | 4 | done |
| `docs/tests/inbox/watch/README.md` | `watch` command cases | 3 | 3 | done |
| `docs/tests/inbox/wait-reply/README.md` | `wait-reply` command cases | 3 | 3 | done |
| `docs/tests/inbox/init/README.md` | `init` command case index | 0 | 0 | done |
| `docs/tests/inbox/init/init-creates-schema-on-empty-db.md` | `init` command case | 1 | 1 | done |
| `docs/tests/inbox/init/init-is-idempotent-on-existing-db.md` | `init` command case | 1 | 1 | done |
| `docs/tests/inbox/send/README.md` | `send` command case index | 0 | 0 | done |
| `docs/tests/inbox/send/send-creates-new-thread.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/send/send-appends-message-to-existing-thread.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/send/send-reads-body-from-body-file.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/send/send-attaches-artifact-with-metadata.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/send/send-rejects-invalid-payload-json.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/send/send-rejects-invalid-artifact-metadata-json.md` | `send` command case | 1 | 1 | done |
| `docs/tests/inbox/fetch/README.md` | `fetch` command case index | 0 | 0 | done |
| `docs/tests/inbox/fetch/fetch-returns-pending-thread-for-target-agent.md` | `fetch` command case | 1 | 1 | done |
| `docs/tests/inbox/fetch/fetch-respects-status-and-limit-filters.md` | `fetch` command case | 1 | 1 | 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
@@ -183,13 +263,13 @@ Recommended order:
1. `docs/tests/inbox/README.md`
2. `docs/tests/inbox/_shared/README.md`
3. `docs/tests/inbox/workflows/README.md`
4. `docs/tests/inbox/send/README.md`
5. `docs/tests/inbox/fetch/README.md`
6. `docs/tests/inbox/claim/README.md`
7. `docs/tests/inbox/reply/README.md`
8. `docs/tests/inbox/done/README.md`
9. `docs/tests/inbox/show/README.md`
10. the remaining command documents
4. `docs/tests/inbox/send/README.md` plus its linked case files
5. `docs/tests/inbox/fetch/README.md` plus its linked case files
6. `docs/tests/inbox/claim/README.md` plus its linked case files
7. `docs/tests/inbox/reply/README.md` plus its linked case files
8. `docs/tests/inbox/done/README.md` plus its linked case files
9. `docs/tests/inbox/show/README.md` plus its linked case files
10. the remaining command indexes and case files
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` | `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/init/README.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/send/README.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/README.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/README.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/fetch/README.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/README.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/claim/README.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/README.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/renew/README.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/README.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/README.md` | `update-moves-thread-to-blocked-with-payload` | blocked update writes a question message and payload JSON | 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/README.md` | `update-rejects-invalid-payload-json` | malformed update payload returns invalid_input | done |
| `docs/tests/inbox/update/README.md` | `update-rejects-non-owner` | non-owner update attempt returns lease_conflict | 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/README.md` | `reply-supports-control-kind` | reply can explicitly send control messages | done |
| `docs/tests/inbox/reply/README.md` | `reply-attaches-artifact` | reply persists artifact data on the message | done |
| `docs/tests/inbox/reply/README.md` | `reply-rejects-invalid-payload-json` | malformed reply payload returns invalid_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/README.md` | `done-persists-result-body-and-artifact` | result body and artifacts remain visible after done | done |
| `docs/tests/inbox/done/README.md` | `done-rejects-non-owner` | non-owner done attempt returns lease_conflict | done |
| `docs/tests/inbox/done/README.md` | `done-rejects-on-terminal-thread` | terminal threads reject repeated done calls | done |
| `docs/tests/inbox/fail/README.md` | `fail-marks-thread-failed` | owner completes a thread into failed terminal state | 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/README.md` | `fail-rejects-non-owner` | non-owner fail attempt returns lease_conflict | done |
| `docs/tests/inbox/fail/README.md` | `fail-rejects-on-terminal-thread` | terminal threads reject repeated fail calls | done |
| `docs/tests/inbox/cancel/README.md` | `cancel-marks-thread-cancelled` | cancel moves a non-terminal thread to cancelled | done |
| `docs/tests/inbox/cancel/README.md` | `cancel-persists-reason-and-artifact` | cancel records its reason text and attachments | done |
| `docs/tests/inbox/cancel/README.md` | `cancel-rejects-when-thread-missing` | missing thread returns not_found on cancel | done |
| `docs/tests/inbox/list/README.md` | `list-filters-by-status` | status filter limits listed threads | done |
| `docs/tests/inbox/list/README.md` | `list-filters-by-created-by` | created-by filter limits listed threads | done |
| `docs/tests/inbox/list/README.md` | `list-filters-by-assigned-to` | assigned-to filter limits listed threads | done |
| `docs/tests/inbox/list/README.md` | `list-respects-limit` | list returns at most the requested number of rows | 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/README.md` | `show-includes-artifacts-per-message` | show expands message artifacts in detail view | done |
| `docs/tests/inbox/show/README.md` | `show-mark-read-advances-read-cursor` | mark-read changes subsequent unread visibility | done |
| `docs/tests/inbox/show/README.md` | `show-rejects-when-thread-missing` | missing thread returns not_found on show | 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/README.md` | `watch-respects-status-filter` | watch only wakes on events whose resulting thread status matches | done |
| `docs/tests/inbox/watch/README.md` | `watch-times-out-with-no-activity` | watch timeout returns no_matching_work | 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/README.md` | `wait-reply-can-start-from-after-event` | wait-reply resumes 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/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/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/send-creates-new-thread.md` | `send-creates-new-thread` | creates a pending thread with an initial task message | 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/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/send-attaches-artifact-with-metadata.md` | `send-attaches-artifact-with-metadata` | persists artifact path, kind, and metadata on send | 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/send-rejects-invalid-artifact-metadata-json.md` | `send-rejects-invalid-artifact-metadata-json` | rejects malformed artifact metadata JSON | 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/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/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/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/claim-acquires-thread-lease.md` | `claim-acquires-thread-lease` | claims a pending thread and records a claim event message | 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/claim-rejects-when-thread-already-claimed.md` | `claim-rejects-when-thread-already-claimed` | active lease conflict returns lease_conflict | 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/renew-extends-active-lease.md` | `renew-extends-active-lease` | owner renews an active lease and gets a renewal event | 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/renew-rejects-without-active-lease.md` | `renew-rejects-without-active-lease` | missing active lease returns invalid_state | 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/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/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/update-rejects-invalid-payload-json.md` | `update-rejects-invalid-payload-json` | rejects malformed `--payload-json` input | 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/reply-adds-answer-message.md` | `reply-adds-answer-message` | appends default `answer` message to an existing non-terminal thread | 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/reply-attaches-artifact.md` | `reply-attaches-artifact` | appends reply message with artifact payload | 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/done-marks-thread-terminal.md` | `done-marks-thread-terminal` | marks a claimed thread as `done` with a result message | 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/done-rejects-non-owner.md` | `done-rejects-non-owner` | rejects `done` from non-owner agent | 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/fail-marks-thread-failed.md` | `fail-marks-thread-failed` | marks a claimed thread as `failed` with a result message | 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/fail-rejects-non-owner.md` | `fail-rejects-non-owner` | rejects `fail` from non-owner agent | 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/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/cancel-persists-reason-and-artifact.md` | `cancel-persists-reason-and-artifact` | persists cancel reason text and attached artifacts | 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/list-filters-by-status.md` | `list-filters-by-status` | filters returned threads by status set | 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/list-filters-by-assigned-to.md` | `list-filters-by-assigned-to` | filters returned threads by current assignee | done |
| `docs/tests/inbox/list/list-respects-limit.md` | `list-respects-limit` | enforces hard cap on returned thread count | 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/show-includes-artifacts-per-message.md` | `show-includes-artifacts-per-message` | expands per-message artifacts in the show payload | 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/show-rejects-when-thread-missing.md` | `show-rejects-when-thread-missing` | returns stable not-found contract for missing thread | 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/watch-respects-status-filter.md` | `watch-respects-status-filter` | wakes only when thread transitions into requested status | 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/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/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/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
@@ -268,16 +348,17 @@ No pending case slugs remain in the current plan.
When a new CLI contract or workflow needs coverage:
1. add the new case to the relevant `README.md`
2. add the new slug to `Authored Case Register`
3. update `Current Snapshot` and `Document Progress`
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. if it is a workflow case, add it to `docs/tests/inbox/workflows/README.md`
3. add the new slug to `Authored Case Register`
4. update `Current Snapshot` and `Document Progress`
## Definition Of Done
This roadmap is complete only when all of the following are true:
- 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
- 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
+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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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
## 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` 不会为缺失线程隐式创建控制消息
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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` 同时更新线程状态与活跃租约
- 成功领取会附带一条事件消息,而不是静默改状态
## 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` 存在
### 断言结论
- 请求的租约时长不是仅用于内部计算,也会被持久化到事件消息中
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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
## 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` 对终态线程是幂等失败,而不是重复成功
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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` 共享结果消息模型,但进入的是失败终态
## 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` 对终态线程不会重复成功
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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` 默认是执行者视角的候选工作列表,不是全局线程扫描
## 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`
### 断言结论
- 空结果不是成功空数组,而是显式的“无匹配工作”信号
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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 的重复初始化不应引入额外迁移失败或状态漂移
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## case: list-filters-by-status
### 用例意义
验证 `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 是硬上限,不会返回超量结果
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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`
### 断言结论
- `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
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `renew-rejects-without-active-lease` | [renew-rejects-without-active-lease.md](./renew-rejects-without-active-lease.md) | missing active lease returns invalid_state |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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` 是线程内追加消息,而不是状态转换命令
## 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 校验
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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
## Scope
This document covers thread creation and message append behavior exposed by `inbox send`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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`
## 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 文档统一说明
# Inbox `send` Test Plan Index
## Case Files
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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` 是线程详情与时间序历史的读取入口
## 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` 或命令参数传入
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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` 会把状态推进和消息追加合并为同一次事务
## 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 所属者
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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` 可以可靠地从既知消息边界之后等待后续答复
## 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
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`.
Shared conventions live in [../_shared/README.md](../_shared/README.md).
## 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` 唤醒结果不仅说明“醒了”,还提供触发该唤醒的具体事件上下文
## 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` 默认从“当前时刻之后”开始等待,不会回放既有事件
| Case Slug | File | Coverage Note |
| --- | --- | --- |
| `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 |
| `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 |
@@ -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`