Execution Log: one Slack surface, two identity allow-lists
Append-only log of progress for the user's visibility.
Phase transitions
| Phase | Entered | Reviewed/approved by | Notes |
|---|---|---|---|
| phase-selection | 2026-08-26 | — | Tier 4 (human-approves-pr): two config schemas, both matched by autonomy.sensitivePaths. Brainstorming skipped — the issue carries the audit table and states the removal exactly |
| requirements-definition | 2026-08-26 | requirements.md — four requirement groups, three abuse cases | |
| design | 2026-08-26 | design.md — schema refusal for the repo file, versioned migration for the operator file; six alternatives recorded | |
| test-planning | 2026-08-26 | testing-plan.md — fourteen rows, eleven applicable | |
| tasks-breakdown | 2026-08-26 | tasks.md — ten tasks | |
| implementation | 2026-08-26 | On claude/github-issue-304-1q1jeu | |
| verification | 2026-08-26 | evidence/verification.md — 2698/2698 tests, ruff + pyright + markdownlint clean, all seven shipped configs valid under real jsonschema, every new test failed against the unfixed tree first | |
| needs-review | 2026-08-26 | PR raised; awaiting the owner | |
| complete |
What was delivered
Seven places to declare Slack- and collaborator-related config; three of them read by any code. The four that were not are gone.
collaborators.yamldescribes people, not delivery. The per-collaboratornotificationssub-object — anenabledswitch, a channeltype, aviatransport and achannel-list— and thenotificationChannelshape behind it are removed from the schema.handle,kindandrolesare untouched: the loop still resolves a phase's reviewers and approvers by role, and that is a process layer, not notification config.- A retired key now says where the thing it configured went.
additionalProperties: falsealready refused the block, but as a bareunknown key— which tells an operator a key is wrong and nothing about the replacement. A smallRETIREDtable beside the validator turns each of the three retired paths into an answer namingchannels.slackandthe-loop migrate-config. It is a dict lookup on the already-failing path, it reads as documentation, and the next removal costs one row. An ordinary typo still gets plainunknown key: guessing at what somebody meant would be worse than silence. - The CLI config's two blocks are the fifth entry in the migration ledger. Same shape as the four before it — site constant,
needs_migrationprobe,assert_currentrefusal,migrate_cli_configremoval — withCURRENT_CONFIG_VERSIONat0.6.0. Detection is by key, not by the version the file claims, so a hand-edited config that stamps0.6.0while keeping the block is still refused. Nothing is converted, because a role list that resolved to no recipient has no equivalent under a channel that subscribes by event name — the report says what to configure instead, and only to an operator who had actually filled the block in. - Everything that promised per-collaborator delivery now says what is true. The templates and this repo's own configs (commented examples included), the collaboration reference, the config pages, the upgrade command's migration checklist, and the capability doc.
docs/config/cli/observability-options.mdreduces toeventLogand gains a section saying where the notifications went;decision-035carries asuperseded in part bymarker, the convention decision-005, decision-021 and decision-030 already set.
Unchanged, and asserted so: channels.slack in shape and behaviour, both identity allow-lists (routing.authorizedUsers, channels.slack.authorizedUsers), and harness-config.yaml's notifications.events as the notify hook's gate. No channel, ingress, dispatch or session code was touched.
Verification
Full results in evidence/verification.md: 2679 → 2698 tests, ruff + ruff format --check + pyright clean, markdownlint clean over 876 docs, all seven shipped configs VALID under real jsonschema, and every new test run against the unfixed tree first and seen to fail there (15 failures, no pre-existing test disturbed).
Documentation
docs/capabilities/channels.md— two behaviour bullets (one Slack surface and two identity allow-lists; the retired shapes and how each is refused) and a History row.docs/config/cli/observability-options.md— reduced toeventLog, plus a "where the notifications went" section; the map row indocs/config/cli/index.mdfollows it.docs/config/harness-config.md— the Collaborators section drops the channel prose and gains a warning explaining the removal.docs/cli/commands/migrate-config.md— the fifth retirement, and the version in the worked example.skills/the-loop/reference/collaboration.md,skills/the-loop/SKILL.md— the paper-trail rule and the personas section say channel, not per-person.commands/upgrade-the-loop.md,commands/init.md,commands/work-on.md,commands/execute-tasks.md— the migration checklist and the notify instructions.docs/decisions/decision-035.md— asuperseded in part bymarker naming exactly which of its decisions no longer stand.
Decisions and open questions
No new decision record was minted. The call — remove rather than wire — is the ticket's own and follows a standing one (issue-245/decision-094 deferred per-person routing explicitly); what changed is that decision-035's points 1 and 2 no longer stand, which the supersession marker records where a reader of that decision will find it.
Three things for the owner at the review gate:
- The ticket's own open question, answered as it proposed. The four daemon event names (
work-item-spawned,dispatch-failed,session-died,event-dropped-unauthorized) were not added to the channelsSUBSCRIBABLE_EVENTScatalog. They never worked, and making them work is a feature wearing a removal's clothes. Worth filing separately; say the word and it gets a ticket. - The ticket's note about
integrations.slackneeds no action. That key was retired in 0.5.0 (issue-245, decision-094) andchannels.slackis its successor — which is what this work item keeps. Nothing here restores the incoming webhook. If the intent was to restore it, that contradicts decision-094 and is a different story. uv.lockcarries one incidental line.uv runrefreshed the editablethe-loopy-oneversion (11.5.0 → 11.6.0, matching the repo's own bump commit) and one dependency marker. Unrelated to this change; kept because anyuv syncreproduces it and dropping it just moves it to the next PR.