Skip to content

Testing plan: drive the loop from Slack — the keywords in the thread, a slash command for the rest

Derived from requirements.md and design.md, before tasks.md. Authored at test-planning; the results section is filled at verification.

This file is executable content. Commands below are what the agent runs; credentials appear by reference only.

Test matrix

#TypeApplies?Scope / what it provesWhere it runs
T1Unityesparse_invocation over every vocabulary row and every refusal (unknown verb, extra tokens, two keywords, two addresses, a collaborator command without a login, a disabled keyword, an operator-renamed keyword); resolve_work_item over the four shapes and the resolved-host rule; may_target over kickoff.repo, a poll repo, a managed work item, a bound conversation, a foreign repo and a failing read; handle_slash_command per family with fakes (the ledger writer, lifecycle, standing, respond): the composed line, the unmarked enveloped record, the facade calls and their rendering, every drop, the duplicate ring, the answer discipline; the catalog rows; the event typesuv run --project cli python -m pytest -q cli/tests/test_channels_commands.py
T2Integration (scenario)yesthrough the listener's handler: /the-loop start #7 records the same comment a thread keyword records and the ingress's parser reads it as start; /the-loop status and /the-loop standing start reach the facade and answer ephemerally; an unlisted member's command leaves nothing; a thread keyword with the grant relays unmarked (R1, pinned)uv run --project cli python -m pytest -q cli/tests/test_channels_integration.py
T3Contract (OpenAPI / GraphQL SDL)n/a — no API route changes; the facade is called, not exposed anew
T4End-to-endn/a — the ledger's ingress executing a relayed keyword is test_routing.py / test_poller.py's subject already; T2 proves the record they read is the one this work item writes
T5UI / visualn/a — Slack's own slash-command UI and plain ephemeral text
T6Snapshotn/a — assertions on call arguments, record bodies and rendered lines
T7Performance / loadn/a — one command is one ledger write or one facade call, acknowledged before handling
T8Security / abuse caseyesone negative test per abuse case A1–A9 (requirements.md § Security considerations)uv run --project cli python -m pytest -q cli/tests/test_channels_commands.py -k "abuse or unauthorized or grant or foreign or duplicate or response_url or grammar or payload"
T9Accessibilityn/a — no UI
T10Migration / upgradeyesa 13.8.0 config parses unchanged (no new key; the two grants are opt-in); both schema copies identical; docs parity; the event catalog knows the new types; the manifest pinned to the guide; the publish table pinned to the cataloguv run --project cli python -m pytest -q cli/tests/test_config_schema_parity.py cli/tests/test_docs_parity.py cli/tests/test_eventlog.py cli/tests/test_channels.py
T11Manual exploratoryn/a — no Slack workspace is reachable from this session; the reviewer's walk-through is the PR briefing's "what to check" and the guide's setup section
T12Lint / format / typecheck / config validation / full suiteyesthe repository's own gates, as pre-commit and CI run themmake check
T13Security review (gate)yesthe-loop checklist against A1–A9, recorded as evidence; tier 3 needs no human sign-off (humanSignOffMinTier: 4)evidence/security-review.md

Scenarios & requirement trace

RowRequirement(s)Scenario / case
T1R2.2test_parse_help, test_parse_instance_verbs, test_parse_standing_verbs, test_parse_work_item_verbs_from_the_configured_keywords, test_parse_refuses_*
T1R2.3test_resolve_work_item_shapes, test_resolve_bare_numbers_need_kickoff_repo, test_resolve_applies_the_resolved_host
T1R2.4, A3test_may_target_*, test_a_failing_read_contributes_nothing
T1R2.5, A6test_parse_refuses_a_malformed_standing_name, test_parse_refuses_a_malformed_address, test_parse_refuses_a_malformed_login
T1R3.1, A1test_an_unlisted_member_is_dropped_before_parsing, test_an_empty_allowlist_denies_everyone
T1R3.2, A2test_a_verb_without_its_grant_is_refused_and_named
T1R3.3, A4test_a_work_item_verb_records_the_composed_line_unmarked, test_the_recorded_line_is_built_from_the_keyword_not_the_text
T1R3.4, A8test_command_events_carry_ids_never_text
T1R3.5test_status_renders_the_facade_document, test_restart_and_upgrade_schedule_through_the_facade, test_standing_verbs_call_the_facade
T1R3.6test_a_failing_ledger_is_a_recorded_outcome, test_a_raising_facade_is_answered_not_raised, test_a_failed_answer_keeps_the_outcome
T1A5test_an_off_host_response_url_is_never_posted_to
T1A7test_restart_passes_one_boolean_and_the_config_path
T1A9test_a_duplicate_trigger_is_dropped
T1R4.1test_the_manifest_is_packaged_and_printed
T2R1.1, R1.2, R3.3Scenario: A slash command start records what a thread keyword records
T2R2.1, R3.5Scenario: A slash command status answers from the facade, Scenario: A slash command starts a standing session
T2R3.1Scenario: An unlisted member's slash command leaves nothing
T2R1.1Scenario: A control keyword in a bound thread relays unmarked for the ingress
T10R4.1, R4.3test_the_guide_reproduces_the_packaged_manifest, test_the_docs_list_every_publishable_event, docs parity, schema parity, event catalog

Verification environment

  • Repositories: this repo only.
  • Services / containers: none. The Slack SDK is faked at its injection points (respond, client_factory); the ledger writer (post_comment) and the core facade (lifecycle, standing) are faked as the existing suites fake them; no tmux, no gh.
  • Fixtures & data: temp directories per test; the fake facade records its calls.
  • Credentials: none. THE_LOOP_SLACK_BOT_TOKEN is set to a dummy value by name where a channel is built.
  • Bring-up: uv sync · Tear-down: none.
  • If bring-up fails: record it under Verification results and escalate.

Evidence plan

RowEvidencePath under evidence/
T1, T2, T8, T10, T12command, counts, duration, raw tail of the output; red → green per taskverification.md
T13the abuse-case table with verdicts and the tests that close eachsecurity-review.md

Verification activities

  • [x] T1 — uv run --project cli python -m pytest -q cli/tests/test_channels_commands.py
  • [x] T2 — uv run --project cli python -m pytest -q cli/tests/test_channels_integration.py
  • [x] T8 — the abuse-case selection above
  • [x] T10 — uv run --project cli python -m pytest -q cli/tests/test_config_schema_parity.py cli/tests/test_docs_parity.py cli/tests/test_eventlog.py cli/tests/test_channels.py
  • [x] T12 — make check
  • [x] T13 — evidence/security-review.md

Verification results

Filled at verification (2026-09-09, head of claude/github-issue-334-k9yw0i).

RowCommandOutcomeEvidence
T1uv run --project cli python -m pytest -q cli/tests/test_channels_commands.pypass — 47 passedevidence/verification.md
T2uv run --project cli python -m pytest -q cli/tests/test_channels_integration.pypass — 24 passed (4 new scenarios)evidence/verification.md
T8the abuse-case selection (-k "abuse or unauthorized or grant or …")pass — 15 passed, A1–A9 each closed by a named testevidence/verification.md, evidence/security-review.md
T10uv run --project cli python -m pytest -q cli/tests/test_config_schema_parity.py cli/tests/test_docs_parity.py cli/tests/test_eventlog.py cli/tests/test_channels.py cli/tests/test_bus.pypass — 162 passed; both schema copies byte-identical; the manifest pinned to the guide; the publish table pinned to the catalogevidence/verification.md
T12make checkpass — ruff, ruff format, markdownlint (1024 files), pyright, validate_config, the full suiteevidence/verification.md
T13the-loop checklistpass — nine abuse cases, nine closed; no human sign-off at tier 3evidence/security-review.md

Review comments

Appended by the-loop's record-feedback hook when a human gate approves with comments (issue-109). Append-only and attributed: an approval never silently discards a reviewer's suggestions, and the feedback travels with the document it concerns rather than living in a side-channel tracker.

Released under the MIT License.