Tasks: the Slack room becomes a colleague
Phase 3. Derived from the approved
requirements.md,design.mdandtesting-plan.md. A DAG: a task lists its dependencies; unlisted ⇒ independent. Each task names the requirement(s) it satisfies and the testing-plan row that proves it. TDD: the test lands with (or before) the change in the same task.
Track A — bug fixes (independent of each other)
- [x] A1 — park at publish (B8). In the graph runtime, entering a human-actor node sets its status to
waitingand persistswork-item-state.jsonin the same step that publishes the approval request;at_human_gate(graphlink.py:274) unchanged. Req: R4 · Test: T2Scenario: the first authorized answer after a gate publishes advances the gate+ T1 unit on the entry path. - [x] A2 — session inherits the daemon's bus (B6). Spawn env (runner.py:379–390) adds
THE_LOOP_CLI_CONFIG= the daemon's resolved config path, skipped when already present/empty;ask(and shared publish path) warns when the bus names no channels. Req: R3 · Test: T2 two Scenarios (spawn env; channel-less warn). - [x] A3 — membership-first channel resolution (B1).
SlackDirectorygains_read_memberships(users.conversations, public+private) consulted before the workspace listing; listings recordtruncated; refusals distinguish truncated/exhausted and name paths + remedy. Req: R1 · Test: T1 unit rows. - [x] A4 — critic timeout (B9). Regression test pinning
--timeoutthreading (the CLI has honoured it since v10 — the ~120 s death was the caller's tool timeout, not the wrapper);critic policyprints each critic's effective timeout; the timeout error names whose limit was hit and how to raise it.critic run --detach/collectdeferred (owner decision, 2026-09-19): R5.1's core is already satisfied and proven, and the error now points at the real cause; detach is a separable follow-up, not built here. Req: R5.1–R5.4 (detach as R5.1's spirit deferred) · Test: T1 rows. - [x] A5 — listener & refusal observability (B3 part). Socket listener logs every ignored envelope (type, channel, reason) at debug;
control.rejectedposts a marked reply with reason + remedy at the attempt site. Req: R2.4, R2.5 · Test: T2 two Scenarios. - [x] A6 — deployment doctor (F2).
probe_subscriptionprints the manifest's bot events + the unverifiability caveat; newdoctor slack: second-consumer heartbeat probe (labelled evidence, not proof) + declared-channel directory check. Deps: A3 (directory truncated flag), A5. Req: R2.1–R2.3 · Test: T1 probe unit + T4 live.
Track B — the room rework (ordered)
- [x] B1 —
Event.summarytransport. Optional field bus→render; escaped, capped, mentions neutralised;ask --summaryand the graph-complete artifact path accept it; digest fallback when absent. Req: R8.1–R8.2, R12.2 (abuse case 3) · Test: T1 escape/cap rows + T8 hostile summary. - [x] B2 —
ChannelStatedelivery memory.last_delivered,progress_ts{phase},gate_ts{node},phase_selection_ts; absent keys ⇒ classic; corrupt file ⇒ classic + one warn. Req: R6 NFR, error handling · Test: T10 degradation row. - [x] B3 —
room_policy.py. Puredecide(event, state): dedupe, gate-collapse, transition-collapse, progress-edit, ack-thread, session-wins, operator-docs; every drop/edit logs its rule. Deps: B2. Req: R6, R10, R11.3, R12.3 · Test: T1 one test per rule row. - [x] B4 —
voice.py+ header removal. Event→(emoji, first-person template) table; rooms lose the header, central channels get short id + title link; single signature at render. Deps: B1. Req: R7, R11.1–R11.2, R11.4 · Test: T1 voice rows. - [x] B5 — delivery integration.
SlackBotChannel.postconsults RoomPolicy:chat.updatefor progress (stale ts ⇒ fresh post), thread replies via storedgate_ts, suppressions applied; GitHub path untouched. Deps: B2–B4. Req: R6, R10 · Test: T2 suppression/edit/thread Scenarios. - [x] B6 — phase selection control (F1). Slack rendering of the checklist as in-message checkboxes + outer-loop element + Execute composing the signed execute (attributed, authorized); typed
execute without <n,…>/skip <phase>grammar; no-interactivity fallback copy; adversarial copy removed. Deps: B5. Req: R9 (abuse cases 2, 4) · Test: T2 checkbox/fallback Scenarios + T1 grammar + T8 unauthorized submit & hostile skip. - [x] B7 — PR gate & digest guard.
pr-review-pendingnames PR number/title/link/diffstat, leads with the briefing, button opens the PR;fitgains protected regions (questions/controls never cut; overflow says what was cut). Deps: B1. Req: R8.3–R8.4 · Test: T1 digest rows + T2 PR-gate Scenario. - [x] B8 —
room.styleconfig.channels.slack.room.style: agentic|classic(default agentic) + schema + docs;classicbyte-preserves today's rendering — existing outbound suites stay green unchanged. Deps: B5. Req: NFR config compat · Test: T10 schema row + full existing suites. - [x] B9 — session voice in the room. With A2 landed:
session.awaiting_inputrenders per R12.1 (question, options, default button); session summary announces the artifact;session-winsproven end-to-end. Deps: A2, B3–B5. Req: R12 · Test: T2 session-voice Scenarios.
Track C — self-service
- [x] C1 — the daemon ensures labels (F3).
github.ensure_labels(idempotent, declared repos only, cached per daemon lifetime) on first contact;set-phase-labelcreates-and-retries once; failure posts once on the ticket. Req: R13 (abuse case 5) · Test: T1 ensure rows + T8 undeclared-repo.
Closing tasks
- [x] D1 — capability docs + decision records. Update affected
docs/capabilities/pages in the same PR; log the two durable decisions (park-at-publish; delivery-policy seam) underdocs/decisions/. Deps: all. - [x] D2 — verification. Execute
testing-plan.md(T1/T2/T8/T10 suites; T4 live re-run with hard redaction; T5 screenshots; T11 verdict); fill Verification results; evidence underevidence/. Deps: all.