slack
Status
Same shape/role as routers/notion.py's own GET /status -- the web dashboard's connect page had no way to poll a Slack connection before this (the CLI's connect-slack.ts instead polls GET /v1/brain/summary's slack_connected flag, which has no team_name and isn't meant as a per-connector status surface). team_name, not workspace_name -- matches SlackConnection's own column name rather than Notion's.
Status › Responses
Successful Response
Update Checkin Channel
The staleness bot's own destination channel (Task 5's Medium check-in, gnt.slack.checkin) -- API-only, no settings UI yet, same scope call routers/github.py's update_default_reviewer and Task 6's identity-mapping endpoints already made for their own admin surfaces.
Update Checkin Channel › Responses
Successful Response
Slash Command
/brain <what you want to capture> — retargeted at proposing rules,
replacing the retired knowledge-unit capture pipeline (triage/extract/
embed) it used to feed. text is freeform;
see _split_command_text above for how it becomes a title/body pair.
The rest goes through create_draft_rule, the exact same function
POST /v1/rules and routers/webhooks.py's ingest_webhook already share
— same sanitize/tenant-scoping/draft-ceiling behavior as every other
draft-rule front door, just reached through a slash command instead.
Bare /brain, /brain help, and /brain status are the three
read-only, zero-write exits from this handler — matched against the
whole trimmed text up front, before anything below touches
create_draft_rule. No new Slack app manifest entry: this reuses the
same /brain command and slash-command endpoint rather than
registering a second one, since /brain had no subcommand-style
parsing to preserve compatibility with. status exists so a human
(or someone verifying the integration works at all) has a way to
check "is this workspace actually linked to an org" without
polluting real data by ingesting a throwaway policy just to find out.
apply_privacy_gate=True below is not optional: a Slack message is exactly the ambient third-party content the gate exists for — someone's words in a channel, not a human deliberately typing straight into gnt — see gnt.pipeline.privacy_gate's module docstring and create_draft_rule's own docstring for the full reasoning, including the masking-is-permanent tradeoff.
No Slack-specific rate limit added on top of the shared
max_draft_rules_per_org ceiling create_draft_rule already enforces.
Deliberate, not an oversight: routers/webhooks.py's per-IP/per-org
limits exist because a webhook ingest token is a leakable bearer
credential (anyone who obtains the URL can call it indefinitely, with
nothing else identifying them), and its per-IP limit specifically
protects the token-lookup DB hit against a flood of requests carrying
invalid tokens. Neither threat model applies here: this endpoint is
only ever reachable through Slack's own signed request (verified
below, before any DB work runs), the caller-identifying signal is
Slack's servers relaying a real signed request rather than any one
IP, and firing this command still requires being a real member of a
workspace that installed the app, typing it into Slack's own UI — a
materially higher-friction, more accountable path than POSTing a
bare URL. The shared draft-rule ceiling is the real backstop for a
compromised or malicious workspace member spamming /brain, same as
it already is for every other draft-rule front door.
Slash Command › Responses
Successful Response
Slack Events
Slack Events API subscription — the inbound half of the staleness
bot's Slack check-in (gnt.slack.checkin). Supports both response
shapes the spec asks for: a threaded reply (a message event whose
thread_ts points back at the check-in's own message) and a reaction
on that message (reaction_added) — each parsed by
classify_response's own simple keyword/reaction lookup, not two
different mechanisms. Anything else (a DM, a reaction on an unrelated
message, a message with no thread_ts, the bot's own messages) is a
plain no-op 200, same as /command's own unknown-subcommand handling.
Same signature verification as /command above (Slack signs every
Events API delivery identically to a slash command) -- verified
before any JSON parsing or DB work, same order /command already
follows.
Always returns 200 -- Slack retries a non-2xx delivery, and neither a signature failure nor an internal error here should make Slack hammer this endpoint; failures are captured to Sentry instead (see the try/ except around the actual dispatch below).
Slack Events › Responses
Successful Response