Tasks: a DM is a channel like any other
Phase 3 of 3. Each task names the requirement it serves and the testing-plan row that proves it. TDD: the red root (task 1) is written and run before any of the three layers exists.
[x] 1. Red root — assert all three layers before any of them exists. New
cli/tests/test_channels_dm.pycarrying the manifest assertion (T1), the kind/findings cases (T2) and thecatchUpSecondsparse cases (T4); newcli/tests/test_channels_dm_integration.pycarrying the listener scenarios (T5) with their Gherkin docstrings. Run both and capture the failures verbatim asevidence/red.md. Requirements: R1.1, R2.1, R2.5, R3.1, R3.2 — Test: T1, T2, T4, T5[x] 2. Subscribe the manifest to every conversation kind.
cli/the_loop/channels/slack-app-manifest.yaml:im:historyandmpim:historyinoauth_config.scopes.bot,message.imandmessage.mpiminsettings.event_subscriptions.bot_events, each with the one-line comment its neighbours carry. Requirements: R1.1, R1.2 — Test: T1[x] 3. Derive the conversation's kind, and say what it needs. In
cli/the_loop/channels/slack.py:CONVERSATION_KINDS(kind → id prefix, bot scope, bot event, human name),channel_kind(channel_id)from the prefix,kind_from_infofrom aconversations.infopayload, and the puresubscription_findings(kind, scopes)returning the sentence of design D3 — no finding when the scopes are unknown. Requirements: R2.1, R2.5 — Test: T2[x] 4. Probe the installed app.
probe_subscription(config, *, client_factory=build_client):conversations.infoon the configured channel,auth.testfor the granted scopes off thex-oauth-scopesresponse header (both header shapes), returning{"skipped": why}on every failure path and never raising. Requirements: R2.2, R2.3 — Test: T3, T10[x] 5. Parse
read.catchUpSeconds.SlackChannelConfig.catch_up_seconds(default 900; explicit0preserved; 1–59 clamped to 60 with a warning; junk → the default), read explicitly rather than through theoridiom its neighbour uses. Requirements: R3.2 — Test: T4[x] 6. Add the schema leaf, in both copies.
read.catchUpSecondsincli/the_loop/schemas/cli-config.schema.json, thencpto.the-loop/cli-config.schema.jsonso the byte-parity test holds. No version bump (design D6). Requirements: R3.2 — Test: T7, T12[x] 7. Probe once at listener start, and reconcile on a deadline.
run_socket_listener: log each finding atwarning(a skipped probe atinfo) afterconnect(), then carry atime.monotonic()deadline through the existing 1-second wait loop so the stop event is still honoured within a tick. Requirements: R2.4, R3.1, R3.3, R3.4 — Test: T5[x] 8. Say it in
channels status.cli/the_loop/commands/channels_cmd.py: the reconcile cadence on theread:line, achannel kind:line and its[!]finding from the prefix alone, and a--probeflag that adds the probed kind, the granted scopes and the confirmed findings. Exit 0 whatever the probe does. Requirements: R2.1, R2.2, R2.3 — Test: T6, T10[x] 9. Documentation that ships with the change.
docs/guide/slack.md: the manifest fence re-synced, the upgrade table's new row, a conversation-kind table, and the Downtime section's reconcile paragraph.docs/config/cli/channels-options.md:read.catchUpSecondswith Type and Default, and the sample YAML.skills/the-loop/templates/cli-config.yaml: the commented key. Requirements: R4.1, R4.2, R4.3 — Test: T12[x] 10. Capability doc + History row.
docs/capabilities/channels.md: the DM behaviour, the doctor and the reconcile as current behaviour, and a History row tracing all three to issue-362. Record it inevidence/documentation.md. Requirements: R4.4 — Test: T12[x] 11. Verify and record. Run T1–T7, T10, then
make check. Completetesting-plan.md's results table and activities checklist; writeevidence/verification.md(including what T9 did not prove and why) andevidence/security-review.mdagainst the six abuse cases. Requirements: all — Test: T12