**Note** from Bead: Slack Integration · [canonical source](https://redfish.acequia.io/guerin/.agents/c915581b-4179-409e-9e13-085531521fd9/2026-06-22/notes/00-slack-integration-design.md) · session 2026-06-22 · discussion: Talk: Slack Integration
## The desire line A human in Slack drops a PDF in a channel. That artifact should be addressable in the commons at a person-scoped URI: ``` COPY slack://redfishgroup/C014PECH5G8/p1781902020952909 → simtable.acequia.io/people/Graydon/newsletter/ ``` That is the verb we want: `COPY` from Slack into the namespace, as if Slack were just another mountable WebDAV-ish resource. The job of this bead is to pave that desire line down to a keyless, deterministic `.mjs` so the AI is the scout that sets it up once, not a toll booth standing in the path of every transfer.
## What the URL actually is (the impedance mismatch) The given URL is a **Slack message permalink**, not a file URL: ``` https://redfishgroup.slack.com/archives/C014PECH5G8/p1781902020952909 workspace channel id message ts ``` - `C014PECH5G8` = channel id. - `p1781902020952909` = message timestamp `1781902020.952909` = **Fri 2026-06-19 20:47:00 UTC**. The PDF is a **file attachment on that message**, not the resource the URL names. You cannot GET the PDF bytes from this URL. Three things stand between the permalink and the bytes: 1. **Resolution.** Permalink → message → the message's `files[]` array → each file's `url_private` (or `url_private_download`). This needs a Slack API call (`conversations.history` with `latest=ts&inclusive=true&limit=1`, or `conversations.replies` if it is a threaded reply). 2. **Auth.** `url_private` is not public. Fetching it requires `Authorization: Bearer xoxb-…` (or a user token `xoxp-…`). No Slack credential exists in `.credentials/` today. 3. **Transport.** Slack is **not a WebDAV server**, so a genuine server-side WebDAV `COPY`/`MOVE` (the nephele third-party-copy path) is impossible. The real operation is always **two-legged**: Slack-API GET → destination WebDAV PUT. So "WebDAV COPY from Slack" is an aspiration about ergonomics, not a literal protocol call. The adapter makes it *feel* like a COPY.
## The adapter (cognition-ladder rung 0) A deterministic Node skill, `slack-to-acequia.mjs`, dependency-free: ``` slackCopy(permalinkOrFileUrl, destDir) 1. parse permalink → { workspace, channel, ts } 2. GET https://slack.com/api/conversations.history?channel=…&latest=ts&inclusive=true&limit=1 (Authorization: Bearer <slack-token>) → message.files[] (fall back to conversations.replies for threaded messages) 3. for each file: GET file.url_private (Authorization: Bearer <slack-token>) → bytes 4. PUT bytes → destDir/<file.name> (Authorization: Bearer <dest-webdav-token>) 5. PUT provenance sidecar → destDir/<file.name>.slack.json ``` Credentials live in nodes, not the commons: the Slack token and the destination WebDAV token are read from `.credentials/`, never embedded in the bead tree. ### Provenance sidecar Every copied artifact gets a `<name>.slack.json` recording where it came from, so the landed file is not an orphan: ```json { "source": "slack", "workspace": "redfishgroup", "channel": "C014PECH5G8", "ts": "1781902020.952909", "permalink": "https://redfishgroup.slack.com/archives/C014PECH5G8/p1781902020952909", "posted_utc": "2026-06-19T20:47:00Z", "author": "<user id / name>", "file": { "id": "F…", "name": "…", "mimetype": "application/pdf", "title": "…" }, "copied_utc": "<stamp>", "dest": "https://simtable.acequia.io/people/Graydon/newsletter/<name>" } ``` This is the asset-catalog instinct (link of URIs) applied per-file, not a STAC manager app. Source URI on one side, landed URI on the other, the message text as the caption.
## Direction-of-flow is symmetric The first example is Slack → acequia (ingest). The same adapter shape runs the other way: acequia → Slack (`files.upload` / `files.completeUploadExternal` to post a namespace artifact into a channel). Worth designing both legs so Slack is a peer endpoint, not a one-way source.
## What we are NOT building - Not a "Slack manager" or a reified "SlackConnector" object. The adapter is a verb (`copy`), a thin riverbed between two namespaces. - Not a polling daemon or a mirror of all of Slack into the namespace. Pull on demand, by URI.
## Decisions (2026-06-22) - **User token, per-parciante.** Use a **user token** (`xoxp-`), not a top-heavy admin bot. Each person runs the skill against whatever Slacks their own access reaches. Onboarding how-to: [get-slack-user-token.md](https://redfish.acequia.io/guerin/.agents/c915581b-4179-409e-9e13-085531521fd9/2026-06-22/skills/get-slack-user-token.md). - **Framing:** auth is the goes-inta / goes-outa membrane; acequia is the core. See [01-auth-goes-inta-goes-outa.md](https://redfish.acequia.io/guerin/.agents/c915581b-4179-409e-9e13-085531521fd9/2026-06-22/notes/01-auth-goes-inta-goes-outa.md). Design both legs (ingest + egress) so Slack is a peer, not a one-way source.
## Open questions (gated on Stephen) 1. **Provision.** Will the `redfishgroup` workspace allow a personal app install, or does it require admin approval? (Affects whether each parciante can self-serve a token.) 2. **Destination filename / overwrite policy.** Keep Slack's `file.name`, or rename? Overwrite if a same-named file already sits in `newsletter/`? 3. **Provenance:** sidecar `.slack.json` as proposed, or fold into an existing catalog convention under `people/Graydon/`?