From 5f593505771a787a3ae63e45b7636243aa1a2d12 Mon Sep 17 00:00:00 2001 From: kurihada Date: Thu, 19 Mar 2026 11:29:17 +0800 Subject: [PATCH] docs: split inbox test plans into case files --- docs/implementation-roadmap.md | 31 ++- docs/tests/inbox/README.md | 17 +- docs/tests/inbox/ROADMAP.md | 261 ++++++++++++------ docs/tests/inbox/cancel/README.md | 92 +----- .../cancel/cancel-marks-thread-cancelled.md | 29 ++ .../cancel-persists-reason-and-artifact.md | 30 ++ .../cancel-rejects-when-thread-missing.md | 27 ++ docs/tests/inbox/claim/README.md | 118 +------- .../claim/claim-acquires-thread-lease.md | 33 +++ .../claim-records-requested-lease-duration.md | 25 ++ ...aim-rejects-when-thread-already-claimed.md | 28 ++ .../claim-rejects-when-thread-missing.md | 28 ++ docs/tests/inbox/done/README.md | 118 +------- .../inbox/done/done-marks-thread-terminal.md | 33 +++ .../done-persists-result-body-and-artifact.md | 34 +++ .../inbox/done/done-rejects-non-owner.md | 25 ++ .../done/done-rejects-on-terminal-thread.md | 25 ++ docs/tests/inbox/fail/README.md | 117 +------- .../inbox/fail/fail-marks-thread-failed.md | 33 +++ ...fail-persists-failure-body-and-artifact.md | 34 +++ .../inbox/fail/fail-rejects-non-owner.md | 25 ++ .../fail/fail-rejects-on-terminal-thread.md | 25 ++ docs/tests/inbox/fetch/README.md | 123 +-------- ...fetch-respects-status-and-limit-filters.md | 31 +++ ...tch-returns-no-matching-work-when-empty.md | 24 ++ ...returns-pending-thread-for-target-agent.md | 30 ++ .../fetch/fetch-unread-uses-read-cursor.md | 34 +++ docs/tests/inbox/init/README.md | 68 +---- .../init/init-creates-schema-on-empty-db.md | 28 ++ .../init/init-is-idempotent-on-existing-db.md | 27 ++ docs/tests/inbox/list/README.md | 113 +------- .../inbox/list/list-filters-by-assigned-to.md | 25 ++ .../inbox/list/list-filters-by-created-by.md | 26 ++ .../inbox/list/list-filters-by-status.md | 26 ++ docs/tests/inbox/list/list-respects-limit.md | 26 ++ docs/tests/inbox/renew/README.md | 92 +----- .../inbox/renew/renew-extends-active-lease.md | 33 +++ .../inbox/renew/renew-rejects-non-owner.md | 24 ++ .../renew-rejects-without-active-lease.md | 26 ++ docs/tests/inbox/reply/README.md | 117 +------- .../inbox/reply/reply-adds-answer-message.md | 34 +++ .../inbox/reply/reply-attaches-artifact.md | 31 +++ .../reply-rejects-invalid-payload-json.md | 25 ++ .../reply/reply-supports-control-kind.md | 25 ++ docs/tests/inbox/send/README.md | 188 +------------ ...send-appends-message-to-existing-thread.md | 33 +++ .../send-attaches-artifact-with-metadata.md | 27 ++ .../inbox/send/send-creates-new-thread.md | 36 +++ .../send/send-reads-body-from-body-file.md | 30 ++ ...-rejects-invalid-artifact-metadata-json.md | 24 ++ .../send/send-rejects-invalid-payload-json.md | 25 ++ docs/tests/inbox/show/README.md | 121 +------- .../show-includes-artifacts-per-message.md | 26 ++ .../show-mark-read-advances-read-cursor.md | 27 ++ .../show/show-rejects-when-thread-missing.md | 26 ++ ...show-returns-thread-and-message-history.md | 29 ++ docs/tests/inbox/update/README.md | 146 +--------- .../update-accepts-body-file-and-artifact.md | 33 +++ ...te-moves-thread-to-blocked-with-payload.md | 27 ++ .../update-moves-thread-to-in-progress.md | 34 +++ .../update-rejects-invalid-payload-json.md | 25 ++ .../inbox/update/update-rejects-non-owner.md | 25 ++ docs/tests/inbox/wait-reply/README.md | 98 +------ .../wait-reply-can-start-from-after-event.md | 29 ++ .../wait-reply-times-out-when-no-reply.md | 28 ++ ...ait-reply-wakes-on-answer-after-message.md | 31 +++ docs/tests/inbox/watch/README.md | 96 +------ .../watch/watch-respects-status-filter.md | 29 ++ .../watch/watch-times-out-with-no-activity.md | 27 ++ .../watch/watch-wakes-on-matching-thread.md | 30 ++ 70 files changed, 1820 insertions(+), 1606 deletions(-) create mode 100644 docs/tests/inbox/cancel/cancel-marks-thread-cancelled.md create mode 100644 docs/tests/inbox/cancel/cancel-persists-reason-and-artifact.md create mode 100644 docs/tests/inbox/cancel/cancel-rejects-when-thread-missing.md create mode 100644 docs/tests/inbox/claim/claim-acquires-thread-lease.md create mode 100644 docs/tests/inbox/claim/claim-records-requested-lease-duration.md create mode 100644 docs/tests/inbox/claim/claim-rejects-when-thread-already-claimed.md create mode 100644 docs/tests/inbox/claim/claim-rejects-when-thread-missing.md create mode 100644 docs/tests/inbox/done/done-marks-thread-terminal.md create mode 100644 docs/tests/inbox/done/done-persists-result-body-and-artifact.md create mode 100644 docs/tests/inbox/done/done-rejects-non-owner.md create mode 100644 docs/tests/inbox/done/done-rejects-on-terminal-thread.md create mode 100644 docs/tests/inbox/fail/fail-marks-thread-failed.md create mode 100644 docs/tests/inbox/fail/fail-persists-failure-body-and-artifact.md create mode 100644 docs/tests/inbox/fail/fail-rejects-non-owner.md create mode 100644 docs/tests/inbox/fail/fail-rejects-on-terminal-thread.md create mode 100644 docs/tests/inbox/fetch/fetch-respects-status-and-limit-filters.md create mode 100644 docs/tests/inbox/fetch/fetch-returns-no-matching-work-when-empty.md create mode 100644 docs/tests/inbox/fetch/fetch-returns-pending-thread-for-target-agent.md create mode 100644 docs/tests/inbox/fetch/fetch-unread-uses-read-cursor.md create mode 100644 docs/tests/inbox/init/init-creates-schema-on-empty-db.md create mode 100644 docs/tests/inbox/init/init-is-idempotent-on-existing-db.md create mode 100644 docs/tests/inbox/list/list-filters-by-assigned-to.md create mode 100644 docs/tests/inbox/list/list-filters-by-created-by.md create mode 100644 docs/tests/inbox/list/list-filters-by-status.md create mode 100644 docs/tests/inbox/list/list-respects-limit.md create mode 100644 docs/tests/inbox/renew/renew-extends-active-lease.md create mode 100644 docs/tests/inbox/renew/renew-rejects-non-owner.md create mode 100644 docs/tests/inbox/renew/renew-rejects-without-active-lease.md create mode 100644 docs/tests/inbox/reply/reply-adds-answer-message.md create mode 100644 docs/tests/inbox/reply/reply-attaches-artifact.md create mode 100644 docs/tests/inbox/reply/reply-rejects-invalid-payload-json.md create mode 100644 docs/tests/inbox/reply/reply-supports-control-kind.md create mode 100644 docs/tests/inbox/send/send-appends-message-to-existing-thread.md create mode 100644 docs/tests/inbox/send/send-attaches-artifact-with-metadata.md create mode 100644 docs/tests/inbox/send/send-creates-new-thread.md create mode 100644 docs/tests/inbox/send/send-reads-body-from-body-file.md create mode 100644 docs/tests/inbox/send/send-rejects-invalid-artifact-metadata-json.md create mode 100644 docs/tests/inbox/send/send-rejects-invalid-payload-json.md create mode 100644 docs/tests/inbox/show/show-includes-artifacts-per-message.md create mode 100644 docs/tests/inbox/show/show-mark-read-advances-read-cursor.md create mode 100644 docs/tests/inbox/show/show-rejects-when-thread-missing.md create mode 100644 docs/tests/inbox/show/show-returns-thread-and-message-history.md create mode 100644 docs/tests/inbox/update/update-accepts-body-file-and-artifact.md create mode 100644 docs/tests/inbox/update/update-moves-thread-to-blocked-with-payload.md create mode 100644 docs/tests/inbox/update/update-moves-thread-to-in-progress.md create mode 100644 docs/tests/inbox/update/update-rejects-invalid-payload-json.md create mode 100644 docs/tests/inbox/update/update-rejects-non-owner.md create mode 100644 docs/tests/inbox/wait-reply/wait-reply-can-start-from-after-event.md create mode 100644 docs/tests/inbox/wait-reply/wait-reply-times-out-when-no-reply.md create mode 100644 docs/tests/inbox/wait-reply/wait-reply-wakes-on-answer-after-message.md create mode 100644 docs/tests/inbox/watch/watch-respects-status-filter.md create mode 100644 docs/tests/inbox/watch/watch-times-out-with-no-activity.md create mode 100644 docs/tests/inbox/watch/watch-wakes-on-matching-thread.md diff --git a/docs/implementation-roadmap.md b/docs/implementation-roadmap.md index 4a3168f..f4a212a 100644 --- a/docs/implementation-roadmap.md +++ b/docs/implementation-roadmap.md @@ -20,7 +20,7 @@ As of now: - `inbox` is implemented end-to-end, including send/fetch/claim/renew/update/reply/done/fail/cancel/list/show/watch/wait-reply - `inbox` supports blocking waits, lease renewal, unread fetches backed by per-agent read cursors, `--body-file`, artifact attachments, and structured JSON errors with stable exit codes - integration tests cover the main inbox lifecycle, wait/watch flows, artifact persistence, and JSON error contracts -- a human-readable inbox command test-plan set has not been authored yet +- a human-readable inbox command test-plan set has been authored under `docs/tests/inbox/` - `orch` currently exists as a command skeleton only - no scheduler workflows have been implemented yet @@ -270,13 +270,10 @@ Definition of done: If a new agent is taking over now, the next concrete step should be: -1. create the inbox test documentation tree under `docs/tests/inbox/` -2. write the shared testing conventions in `docs/tests/inbox/README.md` -3. add `_shared/README.md` for common fixtures and assertion rules -4. add command-level `README.md` files for the implemented inbox commands -5. add `workflows/README.md` for cross-command cases such as unread, wait, and reply flows +1. start `Milestone 4: Orch Core Scheduling` +2. keep the authored inbox test-plan set in `docs/tests/inbox/` synchronized if CLI behavior changes during `orch` work -This is the smallest meaningful documentation slice because the inbox implementation is already present and stable enough to document in detail before `orch` work begins. +The inbox implementation and its human-readable test-plan set are already in place, so the next meaningful project step is to build scheduler behavior on top of that stable base. ## Recommended Driver Choices @@ -302,6 +299,12 @@ Add these tests before the codebase grows too much: ## Inbox Test Documentation Roadmap +Status: + +- completed for the current inbox CLI surface +- command-level and workflow Markdown documents exist under `docs/tests/inbox/` +- future updates should revise this section only when new inbox commands or materially new CLI-visible behavior are added + Goal: - make inbox behavior easy for a new agent to understand and convert into automated tests without re-reading all code paths @@ -312,6 +315,7 @@ Directory layout: - `docs/tests/inbox/_shared/README.md` - `docs/tests/inbox/workflows/README.md` - `docs/tests/inbox//README.md` +- `docs/tests/inbox//.md` Initial command folders: @@ -332,9 +336,11 @@ Initial command folders: Documentation rules: -- organize by folder and `README.md`, not one file per test case +- organize by folder with a `README.md` entrypoint +- command folders use `README.md` as an index only +- each command case lives in its own Markdown file named after the case slug - do not use numeric test case IDs -- identify cases by file path plus a stable case title or `slug` +- identify command cases by concrete file path - keep one command per directory, plus `workflows/` for cross-command behavior - use `_shared/` for common fixtures, database conventions, exit-code rules, and shared JSON assertions @@ -346,9 +352,9 @@ Required per-case structure: - `预期输出` - `断言结论` -Recommended case-title pattern: +Case file naming pattern: -- `## case: ` +- `.md` Authoring order: @@ -382,7 +388,8 @@ Do not block v1 on these: - The design phase is complete enough to start coding. - Avoid reopening major design questions unless implementation forces it. - The repository already has compiling binaries and working schema init. -- Finish the inbox test-plan docs before starting broad `orch` implementation. +- The inbox test-plan docs are in place; keep them synchronized before and during broad `orch` implementation. +- inbox command test-plan folders use `README.md` as an index plus one file per case; keep any further structural changes consistent with the documented rules above. - Preserve the separation: - `inbox` handles communication - `orch` handles scheduling diff --git a/docs/tests/inbox/README.md b/docs/tests/inbox/README.md index 3721956..19c5e52 100644 --- a/docs/tests/inbox/README.md +++ b/docs/tests/inbox/README.md @@ -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 +.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 diff --git a/docs/tests/inbox/ROADMAP.md b/docs/tests/inbox/ROADMAP.md index b125c35..4f2d40d 100644 --- a/docs/tests/inbox/ROADMAP.md +++ b/docs/tests/inbox/ROADMAP.md @@ -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 +.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 `.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 + .md send/ README.md + .md fetch/ README.md + .md claim/ README.md + .md renew/ README.md + .md update/ README.md + .md reply/ README.md + .md done/ README.md + .md fail/ README.md + .md cancel/ README.md + .md list/ README.md + .md show/ README.md + .md watch/ README.md + .md wait-reply/ README.md + .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 `.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 diff --git a/docs/tests/inbox/cancel/README.md b/docs/tests/inbox/cancel/README.md index d2c00a2..32c3e26 100644 --- a/docs/tests/inbox/cancel/README.md +++ b/docs/tests/inbox/cancel/README.md @@ -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 | diff --git a/docs/tests/inbox/cancel/cancel-marks-thread-cancelled.md b/docs/tests/inbox/cancel/cancel-marks-thread-cancelled.md new file mode 100644 index 0000000..b42167d --- /dev/null +++ b/docs/tests/inbox/cancel/cancel-marks-thread-cancelled.md @@ -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` + diff --git a/docs/tests/inbox/cancel/cancel-persists-reason-and-artifact.md b/docs/tests/inbox/cancel/cancel-persists-reason-and-artifact.md new file mode 100644 index 0000000..b26d294 --- /dev/null +++ b/docs/tests/inbox/cancel/cancel-persists-reason-and-artifact.md @@ -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` + diff --git a/docs/tests/inbox/cancel/cancel-rejects-when-thread-missing.md b/docs/tests/inbox/cancel/cancel-rejects-when-thread-missing.md new file mode 100644 index 0000000..b671867 --- /dev/null +++ b/docs/tests/inbox/cancel/cancel-rejects-when-thread-missing.md @@ -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 + diff --git a/docs/tests/inbox/claim/README.md b/docs/tests/inbox/claim/README.md index a69f359..210b21f 100644 --- a/docs/tests/inbox/claim/README.md +++ b/docs/tests/inbox/claim/README.md @@ -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 | diff --git a/docs/tests/inbox/claim/claim-acquires-thread-lease.md b/docs/tests/inbox/claim/claim-acquires-thread-lease.md new file mode 100644 index 0000000..b6079c3 --- /dev/null +++ b/docs/tests/inbox/claim/claim-acquires-thread-lease.md @@ -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` diff --git a/docs/tests/inbox/claim/claim-records-requested-lease-duration.md b/docs/tests/inbox/claim/claim-records-requested-lease-duration.md new file mode 100644 index 0000000..5a34c12 --- /dev/null +++ b/docs/tests/inbox/claim/claim-records-requested-lease-duration.md @@ -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` 存在 + +## 断言结论 + +- 请求的租约时长不是仅用于内部计算,也会被持久化到事件消息中 diff --git a/docs/tests/inbox/claim/claim-rejects-when-thread-already-claimed.md b/docs/tests/inbox/claim/claim-rejects-when-thread-already-claimed.md new file mode 100644 index 0000000..f3d474e --- /dev/null +++ b/docs/tests/inbox/claim/claim-rejects-when-thread-already-claimed.md @@ -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` diff --git a/docs/tests/inbox/claim/claim-rejects-when-thread-missing.md b/docs/tests/inbox/claim/claim-rejects-when-thread-missing.md new file mode 100644 index 0000000..6b269fd --- /dev/null +++ b/docs/tests/inbox/claim/claim-rejects-when-thread-missing.md @@ -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 diff --git a/docs/tests/inbox/done/README.md b/docs/tests/inbox/done/README.md index 02b8f20..300554f 100644 --- a/docs/tests/inbox/done/README.md +++ b/docs/tests/inbox/done/README.md @@ -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 | diff --git a/docs/tests/inbox/done/done-marks-thread-terminal.md b/docs/tests/inbox/done/done-marks-thread-terminal.md new file mode 100644 index 0000000..aeb37de --- /dev/null +++ b/docs/tests/inbox/done/done-marks-thread-terminal.md @@ -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 + diff --git a/docs/tests/inbox/done/done-persists-result-body-and-artifact.md b/docs/tests/inbox/done/done-persists-result-body-and-artifact.md new file mode 100644 index 0000000..ec03ec2 --- /dev/null +++ b/docs/tests/inbox/done/done-persists-result-body-and-artifact.md @@ -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 数量逐个指定”的计数规则 + diff --git a/docs/tests/inbox/done/done-rejects-non-owner.md b/docs/tests/inbox/done/done-rejects-non-owner.md new file mode 100644 index 0000000..e08c9e8 --- /dev/null +++ b/docs/tests/inbox/done/done-rejects-non-owner.md @@ -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 所属者约束 + diff --git a/docs/tests/inbox/done/done-rejects-on-terminal-thread.md b/docs/tests/inbox/done/done-rejects-on-terminal-thread.md new file mode 100644 index 0000000..ce8a46b --- /dev/null +++ b/docs/tests/inbox/done/done-rejects-on-terminal-thread.md @@ -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` 对终态线程是幂等失败,而不是重复成功 + diff --git a/docs/tests/inbox/fail/README.md b/docs/tests/inbox/fail/README.md index da013de..649d091 100644 --- a/docs/tests/inbox/fail/README.md +++ b/docs/tests/inbox/fail/README.md @@ -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 | diff --git a/docs/tests/inbox/fail/fail-marks-thread-failed.md b/docs/tests/inbox/fail/fail-marks-thread-failed.md new file mode 100644 index 0000000..54346b3 --- /dev/null +++ b/docs/tests/inbox/fail/fail-marks-thread-failed.md @@ -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` + diff --git a/docs/tests/inbox/fail/fail-persists-failure-body-and-artifact.md b/docs/tests/inbox/fail/fail-persists-failure-body-and-artifact.md new file mode 100644 index 0000000..629dc1b --- /dev/null +++ b/docs/tests/inbox/fail/fail-persists-failure-body-and-artifact.md @@ -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 对齐” + diff --git a/docs/tests/inbox/fail/fail-rejects-non-owner.md b/docs/tests/inbox/fail/fail-rejects-non-owner.md new file mode 100644 index 0000000..06f0acc --- /dev/null +++ b/docs/tests/inbox/fail/fail-rejects-non-owner.md @@ -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 约束 + diff --git a/docs/tests/inbox/fail/fail-rejects-on-terminal-thread.md b/docs/tests/inbox/fail/fail-rejects-on-terminal-thread.md new file mode 100644 index 0000000..8eee49e --- /dev/null +++ b/docs/tests/inbox/fail/fail-rejects-on-terminal-thread.md @@ -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` 对终态线程不会重复成功 + diff --git a/docs/tests/inbox/fetch/README.md b/docs/tests/inbox/fetch/README.md index 1f738de..5a5ecfd 100644 --- a/docs/tests/inbox/fetch/README.md +++ b/docs/tests/inbox/fetch/README.md @@ -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 | diff --git a/docs/tests/inbox/fetch/fetch-respects-status-and-limit-filters.md b/docs/tests/inbox/fetch/fetch-respects-status-and-limit-filters.md new file mode 100644 index 0000000..23a5c97 --- /dev/null +++ b/docs/tests/inbox/fetch/fetch-respects-status-and-limit-filters.md @@ -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` diff --git a/docs/tests/inbox/fetch/fetch-returns-no-matching-work-when-empty.md b/docs/tests/inbox/fetch/fetch-returns-no-matching-work-when-empty.md new file mode 100644 index 0000000..dda29c0 --- /dev/null +++ b/docs/tests/inbox/fetch/fetch-returns-no-matching-work-when-empty.md @@ -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` + +## 断言结论 + +- 空结果不是成功空数组,而是显式的“无匹配工作”信号 diff --git a/docs/tests/inbox/fetch/fetch-returns-pending-thread-for-target-agent.md b/docs/tests/inbox/fetch/fetch-returns-pending-thread-for-target-agent.md new file mode 100644 index 0000000..ea46c26 --- /dev/null +++ b/docs/tests/inbox/fetch/fetch-returns-pending-thread-for-target-agent.md @@ -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` diff --git a/docs/tests/inbox/fetch/fetch-unread-uses-read-cursor.md b/docs/tests/inbox/fetch/fetch-unread-uses-read-cursor.md new file mode 100644 index 0000000..9ec3a72 --- /dev/null +++ b/docs/tests/inbox/fetch/fetch-unread-uses-read-cursor.md @@ -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` diff --git a/docs/tests/inbox/init/README.md b/docs/tests/inbox/init/README.md index 526a4fb..94e5ee0 100644 --- a/docs/tests/inbox/init/README.md +++ b/docs/tests/inbox/init/README.md @@ -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 | diff --git a/docs/tests/inbox/init/init-creates-schema-on-empty-db.md b/docs/tests/inbox/init/init-creates-schema-on-empty-db.md new file mode 100644 index 0000000..481a40c --- /dev/null +++ b/docs/tests/inbox/init/init-creates-schema-on-empty-db.md @@ -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` 等命令继续使用同一数据库 diff --git a/docs/tests/inbox/init/init-is-idempotent-on-existing-db.md b/docs/tests/inbox/init/init-is-idempotent-on-existing-db.md new file mode 100644 index 0000000..435b7b7 --- /dev/null +++ b/docs/tests/inbox/init/init-is-idempotent-on-existing-db.md @@ -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 的重复初始化不应引入额外迁移失败或状态漂移 diff --git a/docs/tests/inbox/list/README.md b/docs/tests/inbox/list/README.md index aec2382..e1626c8 100644 --- a/docs/tests/inbox/list/README.md +++ b/docs/tests/inbox/list/README.md @@ -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 | diff --git a/docs/tests/inbox/list/list-filters-by-assigned-to.md b/docs/tests/inbox/list/list-filters-by-assigned-to.md new file mode 100644 index 0000000..00ecd6c --- /dev/null +++ b/docs/tests/inbox/list/list-filters-by-assigned-to.md @@ -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` 可用于管理侧查看某位执行者当前承担的线程集合 + diff --git a/docs/tests/inbox/list/list-filters-by-created-by.md b/docs/tests/inbox/list/list-filters-by-created-by.md new file mode 100644 index 0000000..bbb5ebb --- /dev/null +++ b/docs/tests/inbox/list/list-filters-by-created-by.md @@ -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`,而不是成功空数组 + diff --git a/docs/tests/inbox/list/list-filters-by-status.md b/docs/tests/inbox/list/list-filters-by-status.md new file mode 100644 index 0000000..4de38b8 --- /dev/null +++ b/docs/tests/inbox/list/list-filters-by-status.md @@ -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 过滤”的快捷入口 + diff --git a/docs/tests/inbox/list/list-respects-limit.md b/docs/tests/inbox/list/list-respects-limit.md new file mode 100644 index 0000000..89739ee --- /dev/null +++ b/docs/tests/inbox/list/list-respects-limit.md @@ -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` + diff --git a/docs/tests/inbox/renew/README.md b/docs/tests/inbox/renew/README.md index fea88bf..3f54bf3 100644 --- a/docs/tests/inbox/renew/README.md +++ b/docs/tests/inbox/renew/README.md @@ -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 | diff --git a/docs/tests/inbox/renew/renew-extends-active-lease.md b/docs/tests/inbox/renew/renew-extends-active-lease.md new file mode 100644 index 0000000..8bb8bed --- /dev/null +++ b/docs/tests/inbox/renew/renew-extends-active-lease.md @@ -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` 秒默认值处理 diff --git a/docs/tests/inbox/renew/renew-rejects-non-owner.md b/docs/tests/inbox/renew/renew-rejects-non-owner.md new file mode 100644 index 0000000..0ab580e --- /dev/null +++ b/docs/tests/inbox/renew/renew-rejects-non-owner.md @@ -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 约束 diff --git a/docs/tests/inbox/renew/renew-rejects-without-active-lease.md b/docs/tests/inbox/renew/renew-rejects-without-active-lease.md new file mode 100644 index 0000000..527134d --- /dev/null +++ b/docs/tests/inbox/renew/renew-rejects-without-active-lease.md @@ -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 diff --git a/docs/tests/inbox/reply/README.md b/docs/tests/inbox/reply/README.md index 20e5ad9..8d93726 100644 --- a/docs/tests/inbox/reply/README.md +++ b/docs/tests/inbox/reply/README.md @@ -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 | diff --git a/docs/tests/inbox/reply/reply-adds-answer-message.md b/docs/tests/inbox/reply/reply-adds-answer-message.md new file mode 100644 index 0000000..dfa7906 --- /dev/null +++ b/docs/tests/inbox/reply/reply-adds-answer-message.md @@ -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` + diff --git a/docs/tests/inbox/reply/reply-attaches-artifact.md b/docs/tests/inbox/reply/reply-attaches-artifact.md new file mode 100644 index 0000000..9585736 --- /dev/null +++ b/docs/tests/inbox/reply/reply-attaches-artifact.md @@ -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` + diff --git a/docs/tests/inbox/reply/reply-rejects-invalid-payload-json.md b/docs/tests/inbox/reply/reply-rejects-invalid-payload-json.md new file mode 100644 index 0000000..756e98b --- /dev/null +++ b/docs/tests/inbox/reply/reply-rejects-invalid-payload-json.md @@ -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 校验 + diff --git a/docs/tests/inbox/reply/reply-supports-control-kind.md b/docs/tests/inbox/reply/reply-supports-control-kind.md new file mode 100644 index 0000000..6b8c635 --- /dev/null +++ b/docs/tests/inbox/reply/reply-supports-control-kind.md @@ -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` 的消息种类可由调用方显式指定 + diff --git a/docs/tests/inbox/send/README.md b/docs/tests/inbox/send/README.md index 9b8faff..e8b4af0 100644 --- a/docs/tests/inbox/send/README.md +++ b/docs/tests/inbox/send/README.md @@ -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 | diff --git a/docs/tests/inbox/send/send-appends-message-to-existing-thread.md b/docs/tests/inbox/send/send-appends-message-to-existing-thread.md new file mode 100644 index 0000000..9d62571 --- /dev/null +++ b/docs/tests/inbox/send/send-appends-message-to-existing-thread.md @@ -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` diff --git a/docs/tests/inbox/send/send-attaches-artifact-with-metadata.md b/docs/tests/inbox/send/send-attaches-artifact-with-metadata.md new file mode 100644 index 0000000..22814f3 --- /dev/null +++ b/docs/tests/inbox/send/send-attaches-artifact-with-metadata.md @@ -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` 可以在创建消息时持久化附件及其结构化元数据 diff --git a/docs/tests/inbox/send/send-creates-new-thread.md b/docs/tests/inbox/send/send-creates-new-thread.md new file mode 100644 index 0000000..d1985a9 --- /dev/null +++ b/docs/tests/inbox/send/send-creates-new-thread.md @@ -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` diff --git a/docs/tests/inbox/send/send-reads-body-from-body-file.md b/docs/tests/inbox/send/send-reads-body-from-body-file.md new file mode 100644 index 0000000..2b977cd --- /dev/null +++ b/docs/tests/inbox/send/send-reads-body-from-body-file.md @@ -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 文档统一说明 diff --git a/docs/tests/inbox/send/send-rejects-invalid-artifact-metadata-json.md b/docs/tests/inbox/send/send-rejects-invalid-artifact-metadata-json.md new file mode 100644 index 0000000..598fadc --- /dev/null +++ b/docs/tests/inbox/send/send-rejects-invalid-artifact-metadata-json.md @@ -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 合法性 diff --git a/docs/tests/inbox/send/send-rejects-invalid-payload-json.md b/docs/tests/inbox/send/send-rejects-invalid-payload-json.md new file mode 100644 index 0000000..6d7d615 --- /dev/null +++ b/docs/tests/inbox/send/send-rejects-invalid-payload-json.md @@ -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 在写库前就会被拒绝 +- 错误归类为输入问题,而不是内部错误 diff --git a/docs/tests/inbox/show/README.md b/docs/tests/inbox/show/README.md index 913f9ff..2af13ae 100644 --- a/docs/tests/inbox/show/README.md +++ b/docs/tests/inbox/show/README.md @@ -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 | diff --git a/docs/tests/inbox/show/show-includes-artifacts-per-message.md b/docs/tests/inbox/show/show-includes-artifacts-per-message.md new file mode 100644 index 0000000..d86f890 --- /dev/null +++ b/docs/tests/inbox/show/show-includes-artifacts-per-message.md @@ -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 基本字段 + diff --git a/docs/tests/inbox/show/show-mark-read-advances-read-cursor.md b/docs/tests/inbox/show/show-mark-read-advances-read-cursor.md new file mode 100644 index 0000000..a20fbb3 --- /dev/null +++ b/docs/tests/inbox/show/show-mark-read-advances-read-cursor.md @@ -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` 或命令参数传入 + diff --git a/docs/tests/inbox/show/show-rejects-when-thread-missing.md b/docs/tests/inbox/show/show-rejects-when-thread-missing.md new file mode 100644 index 0000000..98f554c --- /dev/null +++ b/docs/tests/inbox/show/show-rejects-when-thread-missing.md @@ -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 + diff --git a/docs/tests/inbox/show/show-returns-thread-and-message-history.md b/docs/tests/inbox/show/show-returns-thread-and-message-history.md new file mode 100644 index 0000000..4d962ed --- /dev/null +++ b/docs/tests/inbox/show/show-returns-thread-and-message-history.md @@ -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 身份 + diff --git a/docs/tests/inbox/update/README.md b/docs/tests/inbox/update/README.md index e9bd049..38f34fd 100644 --- a/docs/tests/inbox/update/README.md +++ b/docs/tests/inbox/update/README.md @@ -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 个 artifact,kind 为 `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 | diff --git a/docs/tests/inbox/update/update-accepts-body-file-and-artifact.md b/docs/tests/inbox/update/update-accepts-body-file-and-artifact.md new file mode 100644 index 0000000..0a4745a --- /dev/null +++ b/docs/tests/inbox/update/update-accepts-body-file-and-artifact.md @@ -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 个 artifact,kind 为 `note` + +## 断言结论 + +- `update` 的正文与 artifact 支持与 `send/reply/done/fail` 保持一致 + +## 补充约束 + +- `--body` 与 `--body-file` 互斥;读取 `body-file` 失败时应返回 `invalid_input` +- `artifact-kind` 与 `artifact-metadata-json` 不能脱离 `--artifact` 单独使用;数量不匹配时也应返回 `invalid_input` + diff --git a/docs/tests/inbox/update/update-moves-thread-to-blocked-with-payload.md b/docs/tests/inbox/update/update-moves-thread-to-blocked-with-payload.md new file mode 100644 index 0000000..66b9e45 --- /dev/null +++ b/docs/tests/inbox/update/update-moves-thread-to-blocked-with-payload.md @@ -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` 更新会生成面向创建者的问题消息 + diff --git a/docs/tests/inbox/update/update-moves-thread-to-in-progress.md b/docs/tests/inbox/update/update-moves-thread-to-in-progress.md new file mode 100644 index 0000000..062c3cb --- /dev/null +++ b/docs/tests/inbox/update/update-moves-thread-to-in-progress.md @@ -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` + diff --git a/docs/tests/inbox/update/update-rejects-invalid-payload-json.md b/docs/tests/inbox/update/update-rejects-invalid-payload-json.md new file mode 100644 index 0000000..e820d8b --- /dev/null +++ b/docs/tests/inbox/update/update-rejects-invalid-payload-json.md @@ -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 约束 + diff --git a/docs/tests/inbox/update/update-rejects-non-owner.md b/docs/tests/inbox/update/update-rejects-non-owner.md new file mode 100644 index 0000000..616590b --- /dev/null +++ b/docs/tests/inbox/update/update-rejects-non-owner.md @@ -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 所属者 + diff --git a/docs/tests/inbox/wait-reply/README.md b/docs/tests/inbox/wait-reply/README.md index 4c65a16..a483f30 100644 --- a/docs/tests/inbox/wait-reply/README.md +++ b/docs/tests/inbox/wait-reply/README.md @@ -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 | diff --git a/docs/tests/inbox/wait-reply/wait-reply-can-start-from-after-event.md b/docs/tests/inbox/wait-reply/wait-reply-can-start-from-after-event.md new file mode 100644 index 0000000..72f982d --- /dev/null +++ b/docs/tests/inbox/wait-reply/wait-reply-can-start-from-after-event.md @@ -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` + diff --git a/docs/tests/inbox/wait-reply/wait-reply-times-out-when-no-reply.md b/docs/tests/inbox/wait-reply/wait-reply-times-out-when-no-reply.md new file mode 100644 index 0000000..178e6f2 --- /dev/null +++ b/docs/tests/inbox/wait-reply/wait-reply-times-out-when-no-reply.md @@ -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` 表示无限等待,而不是立即超时 + diff --git a/docs/tests/inbox/wait-reply/wait-reply-wakes-on-answer-after-message.md b/docs/tests/inbox/wait-reply/wait-reply-wakes-on-answer-after-message.md new file mode 100644 index 0000000..9a1a638 --- /dev/null +++ b/docs/tests/inbox/wait-reply/wait-reply-wakes-on-answer-after-message.md @@ -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 + diff --git a/docs/tests/inbox/watch/README.md b/docs/tests/inbox/watch/README.md index 170a7d7..f850bfa 100644 --- a/docs/tests/inbox/watch/README.md +++ b/docs/tests/inbox/watch/README.md @@ -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 | diff --git a/docs/tests/inbox/watch/watch-respects-status-filter.md b/docs/tests/inbox/watch/watch-respects-status-filter.md new file mode 100644 index 0000000..ba2d5ab --- /dev/null +++ b/docs/tests/inbox/watch/watch-respects-status-filter.md @@ -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` 会从该事件游标之后恢复,允许调用方断点续看 + diff --git a/docs/tests/inbox/watch/watch-times-out-with-no-activity.md b/docs/tests/inbox/watch/watch-times-out-with-no-activity.md new file mode 100644 index 0000000..368d64e --- /dev/null +++ b/docs/tests/inbox/watch/watch-times-out-with-no-activity.md @@ -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` 默认从“当前时刻之后”开始等待,不会回放既有事件 + diff --git a/docs/tests/inbox/watch/watch-wakes-on-matching-thread.md b/docs/tests/inbox/watch/watch-wakes-on-matching-thread.md new file mode 100644 index 0000000..04bd0b1 --- /dev/null +++ b/docs/tests/inbox/watch/watch-wakes-on-matching-thread.md @@ -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` +