Migrate Slack secrets into credentials
## Original requirement (verbatim, epic 13348)
Deep research all third party integrations in Lovable (Connect) and implement all of them.
Ensure the highest level of code reuse, abstract out any generic code across all integrations. Users need an easy way to connect. Documentation must be as detailed as in Lovable or better.
If something needs to be done on the partner website (e.g. register an app) and it can only be done by human create a follow up task per third party with exact step by step of what user needs to do). Mark it as user only task to set up but the runner will validate it was done correctly.
You can refactor anything to make working with creds more robust.
## Settled inputs
Full PRD and architecture: the epic's `prd` and `architecture` artifacts (Task 13348 BA, Task 13349 Architect). Section numbers below (§3.2 item 6, AC2.3, …) refer to them. They are settled: realize them, do not reopen them. Operator decisions O1–O6 and D1–D8 bind.
## This child's scope
C7, architecture §6 Slack row, release N only (the column drop is the final retirement child).
- Credential type `slack_workspace` (fields `bot_token` secret, `team_id`, `team_name`, `app_id`, `bot_user_id`), org-owned and `system_owned`; never offered as the operator-plane `slack` app type.
- `slack_installations.credential_id` already exists from C1's revision (no migration in this child).
- `apps/api/app/integrations/legacy_adopt.py::adopt_slack`: idempotent re-seal job opening the tier-1 envelope with app code (row-bound AAD — SQL cannot move it), writing the credential, setting `credential_id`; registered as a one-shot in the cron system run from the API startup hook; counts (adopted, skipped, marked needs-reconnect, pending) written to the cron's operator system-activity record; unopenable rows marked needs_reconnect with a reason, never dropped; logs counts only.
- Readers (`slack_workspaces.py`, the socket) prefer the credential and fall back to `token_sealed`.
- Slack connect runs on the shared engine via `OAuthProvider.on_grant = slack_workspaces.on_grant` (exchange through the plan-gated `slack_egress` seam; writes the system-owned row + `slack_installations`). `components/SlackWorkspaceCard.tsx` keeps working (the retirement child later turns it into a link). New installs write the credential (and, during the overlap release, still whatever the old reader needs).
- Callback compatibility: the redirect URI already registered at the vendor for this flow must keep working (route it into the shared callback handler) so the move needs NO partner-site change; if a vendor-side change is unavoidable, say so in the PR and report so a human setup task can be filed.
Security surface: autonomous security review lane.
Credentials & reach:
- C19 precondition (legacy re-seal complete): the operator console's system-activity record via `org/console/dev-login` on app.dev.jaah.ai — verified reach; the record itself is created by C7 and C8
- Unit tests, `./dc check quick`, `tools/check_context.py` — verified (no credential)
Out of scope (PRD §6, verbatim, plus what NOT to touch):
- No app-user connectors (per-end-user OAuth, connector gateway for published apps).
- No "Managed by Jaah" vendor keys or credit billing; no change to `apps/api/app/billing/`.
- No per-vendor API client libraries or MCP tool wrappers built by Jaah (except where the vendor's own hosted MCP server is the connector).
- No Lovable action-approval UI (allow once/always).
- No change to runner auth to model providers (`account_auth.py`, `Account` table) or to login/sign-in OAuth (`routes/login_oauth.py`).
- No change to NodeVault crypto semantics (`credential_crypto.py` encryption tiers) beyond what a refactor needs to keep behaviour identical.
- Do not break existing rows: every existing credential type and every existing row keeps working; migrations additive (two-release overlap rule).
- Supabase/project-backend and git-sync are not new work; Figma plugin/.fig upload out.
- Never write a credential value into a task, doc, log or PR.
- Do NOT file Tasks (no licence): the human-only OAuth registration tasks are filed outside this child.
- Do NOT drop `token_sealed` here (release N+1, the retirement child).
Done means:
- AC1.3 (Slack) The bespoke Slack credential-filling connect path runs on the shared engine. — `grep` for Slack authorize/exchange code outside the shared engine + its `on_grant` hook returns nothing; unit test drives a Slack connect through `/api/integrations/slack/connect/start` (the unlisted-provider path from C1; W1 adds the listed card later) and the shared callback.
- No-break: an existing installation keeps sending and receiving — unit test: a pre-migration row is adopted, readers return the same token via the credential; a second run adopts nothing (idempotent); an unopenable row ends `needs_reconnect` with a reason and is counted.
- The re-seal counts land in the system-activity record — unit test asserts the counts.
- `credential_get` on a migrated Slack row returns 404 (system-owned) — unit test.
- After deploy to dev, the operator console's system-activity record for the job shows pending = 0 (read with `org/console/dev-login` on app.dev.jaah.ai) — cite it in the report; this is the retirement child's precondition.
- AC1.5 `./dc check quick` green and `tools/check_context.py` green on this PR — the gate output cited in the PR body.
Activity
- Draft
- Ready
- Runner started