Self-serve team onboarding for Darwin#58
Open
rajivml wants to merge 41 commits into
Open
Conversation
…auto-provision Anyone can submit an onboarding request; admins approve. On approval we auto-provision connectors (web docs all-versions, Confluence, GitHub, Slack), an assistant (Orchestrator prompt by default, requester-editable), a Slack bot config (optional SME review / oncall / Jira), and high-priority indexing, then expose per-source scrape status the requester can monitor. - backend: OnboardingRequest model + status enum (stored as String to avoid the native_enum NAME/value gotcha), migration c40f5a073fc2, db/onboarding.py CRUD, onboarding/validation.py (inline Slack channel/group, Confluence space, docs root validation) + onboarding/provision.py orchestrator, and the /onboarding + /admin/onboarding API. - security: require an authenticated user on submit/validate/status (reject anonymous / API-key service callers) and a Confluence host allowlist so the stored Confluence token is only ever sent to a host an existing Confluence connector already uses (SSRF / credential-leak guard). - web: /onboarding request form (inline field validation, cloud+on-prem docs roots, editable prompt, SME/oncall/Jira toggles, my-requests + scrape status) and /admin/onboarding approval queue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Slack docs were indexed with the workspace stored as its display name
("Product", plus a "Product " space variant), so every citation permalink
pointed at the dead host product.slack.com. ~320k docs (913 channels)
affected; all verified to genuinely live in the uipath-product workspace.
Two layers, no re-index / data migration required:
- Read-time: normalize_slack_link() rewrites the known-bad "product" subdomain
to "uipath-product" via an explicit allow-map (case/space-insensitive), applied
in _vespa_hit_to_inference_chunk. Every citation (chat, search, Slack bot)
derives its link from source_links, so one choke point fixes all three.
Genuinely different workspaces (uipath-customer-ops, uipath-marketing, ...)
are left untouched.
- Index-time: get_all_docs now resolves each channel's true workspace subdomain
once via chat.getPermalink (Grid-correct), so new scrapes are right regardless
of the configured workspace; the configured value is only a fallback.
Verified on prod (read-only): normalized links matched Slack's authoritative
chat.getPermalink for 40/40 sampled docs, and the real keyword_retrieval path
returned 10/10 uipath-product links.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…call resolve_workspace_subdomain called client.chat_getPermalink raw, bypassing the make_slack_api_rate_limited / make_slack_api_call_logged wrappers that every other Slack API call in this connector uses. On a 429 it would fall back to the configured workspace instead of retrying with Retry-After. Wrap it so a new workspace's first scrape resolves correctly under rate limiting. Index-time only (one call per channel); no behavior change to the read-time citation fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tial-access preflight
Server-side validation at the point the credential is actually used, since
submit-time /validate can be bypassed by a direct API call:
- SSRF / credential-leak guards in provision.py `_build_connector_base`:
* Confluence: only build a connector for a host an existing Confluence
connector already uses (fail closed) — never send the stored token to an
arbitrary host.
* GitHub: require a github.com URL.
* Web: reject non-http(s) and hosts resolving to private/loopback/link-local/
reserved addresses (block crawling internal infra with our outbound identity).
- Credential-access preflight: verify the reusable shared credential can actually
reach each source before provisioning, so we never create a connector that
silently indexes nothing. New validate_github_repo() (token -> get_repo, sent
only to the GitHub API, generic errors, no token/payload leakage) wired into
/validate; provision `_assert_source_accessible` reuses the Confluence/GitHub/
Slack validators and fails the request with a clear reason.
- Frontend: GitHub sources are now live-validated inline (were format-only).
30 onboarding unit tests (host/SSRF rejects, cred-access success/fallback,
secret-leakage assertion); mypy + pre-commit clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Onboard a Team" link in the chat sidebar (next to Manage Assistants) and a FiUserPlus icon in the chat top-right, both linking to /onboarding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Form changes (per product review): - Title -> "Onboarding Darwin to Slack Channel". - Bot-channel field gets an info tooltip (where the Slack bot is configured). - Response format is standardized to citations for all channels (selector removed). - Removed "respond only when tagged" and "enable Jira ticket creation" options. - Channel message-history indexing is now always on (toggle hidden). - Docs fields renamed "Docs-URL - Cloud" / "Docs-URL - On-prem"; "Sources (priority order)" -> "Sources". - Removed GitHub from additional sources; added Jira (JQL filter) as a source, validated live before submit. Jira source (backend): validate_jira_filter (JQL run via the stored Jira credential against an existing connector's base URL only; generic errors, no token/payload leakage) wired into /validate; provision builds a Jira connector (reused base URL + JQL) and preflights access. Credential-key + source-type maps updated. UI polish (frontend-design): numbered step sections, consistent field component with inline validation states, refined source rows. Uses only the app's semantic theme tokens, so the page honors the global light/dark setting automatically. 34 onboarding unit tests; mypy, tsc, next lint, pre-commit all clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…checks + theme toggle - Channel validation: the old bare-name path scanned conversations_list capped at 12 pages (~12k channels); on Enterprise Grid a channel can be far deeper (help-automation-suite is on page 19), so valid channels read as "not found". Slack has no name->channel lookup API and every channel-read needs the ID, but provisioning only uses the channel *name* — so instead of enumerating the whole org we now search only the channels the bot is a MEMBER of (users.conversations, a small set), which is also the real precondition for the bot to operate there. Not-found now says to invite the bot rather than "paste a #mention". - Client-side validation: instant format hints (no #, lowercase, https://, JQL), debounced backend validation (no need to blur), and Submit disabled until the bot channel resolves. - Theme: added a light/dark toggle to the page (it already honored the global theme, but rendered outside the app chrome so had no control). Mirrors UserDropdown — same darwin-theme key + .dark class. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bot is only added to the channel DURING onboarding (connector auto-join on
index), so a brand-new channel legitimately isn't a member yet — requiring
membership would reject exactly the channels being onboarded. Since Slack has no
name->channel lookup and provisioning uses the channel name (not id), the channel
check no longer blocks on existence: the membership check is a positive signal
only ("bot is already in this channel"), and any well-formed name is accepted
with a "the bot will be added during onboarding" note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…teps A bare channel name can't be verified reliably (Slack has no name lookup), but a channel link carries the channel id, so conversations_info verifies it exactly in one call — even for a public channel the bot isn't a member of yet. So the bot channel now requires a link: - backend: parse a pasted channel URL (/archives/C…) to extract the id and verify via conversations_info (alongside the existing <#…> mention and raw-id paths). - frontend: the bot-channel field requires a link (client check rejects a bare name before any round-trip, so the field can't resolve until a link is given), with visible steps: open the channel -> ⋯ (More) -> Copy -> Copy link. Additional Slack *source* rows still accept names (the connector auto-joins by name on index); only the bot channel is link-gated. Verified live: a pasted link resolves to the correct channel via conversations_info. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…default scraping Label "Bot channel link" -> "Slack Channel"; info tooltip now states the channel is scraped by default and kept updated so its knowledge stays current. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The on-call option now accepts Slack user-group handles (e.g. @as-dri), not just an OpsGenie schedule. Handles are stored in the channel's follow_up_tags, which the "I need more help" button already resolves (emails->users, names/@handles-> user-groups) and @-mentions. OpsGenie schedule remains as an optional/additional source of the current DRI. - models: OncallOption.handles (comma-separated). - provision: _build_channel_config sets follow_up_tags from the handles. - validation: validate_slack_group now validates a comma-separated list, matching either group name or @handle, and reports which ones don't resolve (also fixes the SME multi-group field). - frontend: "DRI Slack handle(s)" field (validated live) under the on-call option. mypy, 34 tests, tsc, eslint, pre-commit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On submit, post a best-effort Slack message to the ops channel (default "darwin-devs", override via ONBOARDING_NOTIFY_CHANNEL) with the requester, team, target channel, sources, and a link to the admin review console. Wrapped so a Slack failure never fails the submission. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
darwin-devs is a private channel in a different Grid workspace; the DanswerBot app token (fetch_tokens, which notify uses) is a member, but a private/ cross-workspace channel must be addressed by ID, not name. Default ONBOARDING_NOTIFY_CHANNEL to the channel ID (C07B2V8E99S), and accept an id, name, or pasted channel link (id parsed out) via env/configmap. Verified: the DanswerBot token resolves C07B2V8E99S (#darwin-devs, is_member=True). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Darwin now goes live in a channel only once its knowledge is actually scraped. Phase 1 (on approval, provision_onboarding): create connectors + cc_pairs and kick off high-priority indexing; status INDEXING. No doc set / assistant / Slack config yet. Phase 2 (background sweep, finalize_ready_onboarding_requests, ~every 60s in the indexing scheduler loop): once every source's latest index attempt succeeds, create the document set over the cc_pairs, the assistant (prompt + persona) tied to it, and the Slack bot config — making Darwin live — then mark COMPLETE and notify #darwin-devs. Source failures flip the request to FAILED and notify once. Recovery needs no restart: the sweep also re-checks FAILED requests, so fixing + re-indexing a failed source lets it finalize automatically on a later tick. Also: - Per-source scrape cadence: Slack/Confluence/Jira daily, docs (web) monthly. - Confluence parent/child dedup: a whole-space URL supersedes its pages, and a parent page supersedes descendant pages (best-effort via the ancestry API) — keeping the parent alone, since the connector already recurses child pages. - Status endpoint no longer auto-advances; the finalizer owns transitions. 45 onboarding unit tests; mypy + pre-commit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Positive + negative paths for _source_index_state (all-success, one-failed, in-progress, no-attempt-yet, no-cc-pairs) and finalize_ready_onboarding_requests (finalize on success, flag failure once, don't re-notify an already-FAILED request, recover a previously-FAILED request once sources succeed, no-op while still indexing, and per-request error isolation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… checks Backend (mocked bot/atlassian clients — no live API): - validate_slack_channel: link / <#…> mention / id -> conversations_info, bad id rejected, bare name that the bot is in vs not-yet-in (accepted), empty. - validate_slack_group: comma-separated handles all-found vs some-missing, empty. - validate_confluence_url: bad host rejected, unverified without creds, space accessible, space inaccessible (+ asserts no token/exception leakage). Frontend: extracted the form's client-side format checks into lib/onboarding/clientChecks.ts (checkChannelLink/checkChannel/checkUrl/checkJql + SOURCE_CLIENT_CHECK) and added a vitest suite (positive + negative for each). 69 backend onboarding tests + 11 frontend tests; mypy, tsc, eslint, pre-commit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The requester page rendered without an auth check (only the backend APIs were gated), so a signed-out user saw a form whose every action 401s. Add the app's standard server-side gate (getCurrentUserSS + redirect to /auth/login), matching search/chat and the admin Layout. Allowlist /onboarding for the post-login `next` redirect so users land back on the form after SSO. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Light mode uses white cards (bg-background-weak #fff) on a near-white canvas (bg-background #f1f3f7); with only a hairline border and no shadow the cards didn't separate and the page read as one flat, over-bright expanse. Add shadow-sm to the section + request cards so they lift off the canvas. No effect in dark mode (shadows are invisible there; the border already defines the cards). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…channels Scraping needs the bot in the channel. The connector auto-joins PUBLIC channels on scrape but can't self-join PRIVATE ones — those must be invited. Reflect that in the channel validation message using is_member / is_private from conversations_info: - member -> "the bot is already in this channel" - public, not member -> "the bot will auto-join this public channel when it scrapes" - private, not member -> "invite @darwin — private channels can't be auto-joined" - conversations_info error -> hint to invite the bot for private channels Bare-name (source rows) note now says public auto-join / invite-if-private. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cope The message said "Space '<id>' (existence unverified)" even for a PAGE url, wrongly implying a whole-space scrape. Mirror the connector's actual behavior: - page url -> "Will scrape this page and all its child pages" - space url -> "Will scrape the whole '<space>' space" Also verify the actual target (get_page_by_id for a page url, get_space for a space url) instead of always checking the space. resolved now includes page_id. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ndling - On-prem docs field now shows an automation-suite reference URL (https://docs.uipath.com/<product>/automation-suite/<latest-version>) alongside the standalone example. - Help text: for automation-suite docs the version is stripped and Darwin scrapes the latest 3 versions automatically — no need to list each version URL. - Provision now sets max_versions=3 (was 2) to match, so the latest 3 concrete versions are crawled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"The bot channel's own message history is always indexed" was under the docs URLs, where it's confusing — it's about the Slack channel, not docs. That fact is already stated in the Slack Channel field's info tooltip, so remove the duplicate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Admins couldn't find the onboarding review page. Add "Onboarding requests" to the admin sidebar (Custom Assistants group, next to Slack Bots) -> /admin/onboarding, which lists all requests with status and approve/reject actions. So approval is a UI action, not only a Slack thing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The page rendered only the form, so there was no nav — an admin couldn't reach the Admin Panel, and there was no logo/user menu. Render the standard <Header> (logo -> chat, UserDropdown with Admin Panel for admins + theme toggle + logout). Removed the now-redundant inline theme toggle from the form (the header provides it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds GET /admin/onboarding/{id} (full payload) and PATCH /admin/onboarding/{id}
(replace payload, pending-only) plus update_onboarding_payload, so an admin can
open a request in the form, fix gaps, and approve. Additive; wired to UI next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…av link - Onboarding page now has top tabs: "Submit a request" and "My requests (N)", so status isn't buried at the bottom. - On the "My requests" tab, each request's per-source scrape status auto-loads and re-polls every 15s (no manual "refresh" needed) so the requester sees work happening — plus an empty state when they have none. - Left nav: added "My onboarding requests" to the chat sidebar, deep-linking to the requests tab (/onboarding?view=requests). OnboardingForm wrapped in Suspense for the useSearchParams read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reuse OnboardingForm for both flows via an optional initialRequest prop: - requester create mode (unchanged), and - admin edit mode — prefilled from the request; tabs/"my requests"/default-prompt fetch are suppressed; submit becomes "Save & approve" (PATCH the payload, then approve+provision, redirect to the queue), plus "Save changes" (PATCH only) and "Cancel". New admin route /admin/onboarding/[id] loads the request (GET admin endpoint) and renders the form in edit mode (Suspense-wrapped for useSearchParams). The admin queue table gets an "Open / edit" action on pending rows. Channel prefills from the stored channel id (re-validates via the link path); sources round-trip (docs + extras editable as rows, channel history re-derived). tsc, eslint, pre-commit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Requesters can now fix details on a still-pending request (shared form, editMode="requester") or withdraw it. Cancel is a soft cancel: status flips to CANCELLED (kept for the audit trail, greyed in the list); edit and cancel are pending-only and owner-or-admin scoped server-side. Approved/indexing requests stay admin-only. No DB migration (status is a plain String column). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… its connector
Admins can click a source name under "Refresh scrape status" to jump
straight to /admin/connector/{cc_pair_id} for full indexing status and
error detail, instead of reading the summarized row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…otify links - provision_onboarding: record the real cc_pair id (this fork's add_credential_to_connector returns connector_id, not the pair id) so the finalizer/status track the correct sources. - finalize_onboarding: idempotent + incremental — create doc set / assistant / Slack config only if not already recorded, persist each id immediately as a checkpoint, and drop the finalizer's read txn before insert_document_set's begin(). A pod crash mid-finalize now resumes instead of duplicating artifacts. Notification failure no longer fails finalize. - finalizer sweep: in-progress wins over failed (never show FAILED while a source is still scraping); recover FAILED -> INDEXING on re-index. - notify: submitted -> request form; complete -> status queue; drop sources line. - tests: idempotency/resume/crash-recovery/notify-failure + state-machine. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
start_playwright honors WEB_CONNECTOR_BROWSER_CHANNEL (unset in prod, so the pinned bundled Chromium is used as before). Local dev sets it to "chrome" because the chromium-1097 build for playwright 1.41.2 SIGSEGVs on recent macOS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y completed form - OnboardingJourney: milestone stepper (submitted -> approved -> indexed -> document set -> assistant -> slack bot -> live) derived from request state, with links to each artifact once created. Shown in admin queue + My requests. - SourceStatusSummary: aggregate rollup (e.g. "4/6 succeeded"); shown by status. - Admin queue: status filter pills (default hides finished) + pagination (10); auto-load per-source status; connector hyperlinks; aligned wrapping columns. - Completed/non-pending requests: form shown read-only (Save & approve disabled). - Slack bot config: selected document sets visually distinct (accent + check). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post a single root message to the customer channel on submit (@mention the requester + tracking link), then reply IN THAT THREAD on approval / complete / failure — never new top-level messages, so a large channel isn't spammed. - onboarding_request.help_thread_ts column (+ migration a1b2c3d4e5f6) stores the root message ts so later updates thread under it. - notify: ONBOARDING_CLIENT_CHANNEL (defaults to darwin-devs for testing; set to #help-darwin in prod), notify_client_submitted/approved/complete/failed. @mention via users.lookupByEmail with email fallback; self-joins public channels; skips replies entirely when no thread ts (never posts top-level). - wired into submit endpoint (root + persist ts), provision_onboarding (approved), and the finalizer (complete/failed). Best-effort; never fails the request. Internal #darwin-devs ops notifications unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Annotate the _wire `state` param and add type: ignores for the SimpleNamespace fixtures passed where OnboardingRequest is expected — keeps the new tests clean under the PR-gate `mypy .`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… channel Roll out the self-serve onboarding feature (+ merged slack citation fixes): - images backend vha-218 -> vha-219, web vha-116 -> vha-117 - env-configmap: ONBOARDING_CLIENT_CHANNEL=#help-darwin (customer thread), ONBOARDING_NOTIFY_CHANNEL=#darwin-devs (internal ops) Applied + validated: alembic head a1b2c3d4e5f6, routes mounted, scheduler clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
finalize_onboarding created the persona with the document set but no tools, so upsert_persona never enabled search — the assistant had the doc set but couldn't search it. Look up the built-in SearchTool (in_code_tool_id="SearchTool") and pass tool_ids so search is enabled. The startup auto-add migration doesn't cover personas created later at finalize time. Test asserts the persona gets tool_ids. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploy the fix so provisioned onboarding assistants get the built-in SearchTool. Backend-only; web stays vha-117. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Satisfies the CI-pinned prettier (the file was committed with a different prettier version). Formatting only — no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Self-serve team onboarding for Darwin
Adds an end-to-end self-serve onboarding flow: a validated request form → admin
approval → automatic provisioning (connectors, document set, assistant, Slack
bot config) → background finalizer, with Slack notifications throughout.
Highlights
Confluence, Jira, GitHub sources); requester self-edit/cancel of pending
requests; admin edit-and-approve in the same shared form; SSO-gated.
indexing → INDEXING; background finalizer creates the document set, assistant
(prompt + persona with the SearchTool), and Slack bot config → COMPLETE.
Idempotent + checkpointed so a pod crash resumes without duplicating artifacts;
"in-progress wins over failed" so a request never shows FAILED while a source
is still scraping.
with connector links, a milestone "journey" stepper, aggregate rollups, and a
read-only view for finished requests.
errors); customer-facing thread → #help-darwin (requester @mentioned on submit,
lifecycle updates as threaded replies; channel configurable via
ONBOARDING_CLIENT_CHANNEL).feature/slack_link_fix)so this branch doesn't regress them.
Ops
onboarding_request(+help_thread_ts) — additive migrations.ONBOARDING_NOTIFY_CHANNEL,ONBOARDING_CLIENT_CHANNEL.Tests
Onboarding unit tests cover the finalizer state machine (idempotency, resume,
crash-recovery, notify-failure, SearchTool attachment), notifications, provision
builders + off-by-one regression, and frontend client checks.