# Chatbot V1 — User Allow-List, 8 Capabilities, Audit Log Built on top of the [MVP](./chatbot-mvp.md). V1 ships: - 8 capabilities (MVP 3 + `data_read` + 4 high-risk). - Per-user override via the new `TenantChatbotAccess` table. - Audit-event-per-high-risk-call through `@toolctrl/db/audit-events.server`. - Per-capability sliding-window rate-limits via `@toolctrl/shared-store`. ## Capability matrix | Capability | Risk | Tool name | Notes | | ------------------ | ------ | ---------------------- | ------------------------------------------------------------------ | | `qa_help` | low | `search_help` | Bundled docs ranker. No DB. | | `kb_search` | low | `search_knowledge_base`| Tenant-scoped KB. Wraps results in `---KB ARTICLE---` markers. | | `account_info` | low | `get_account_info` | Whitelist projection. Never returns billing tokens / customer ids. | | `data_read` | medium | `read_entities` | Strict entity enum, tenant-scoped read. | | `data_write` | high | `write_entity` | Audit + rate-limit (30/min default). | | `workflow_trigger` | high | `start_workflow` | Audit + rate-limit (10/min default). | | `admin_actions` | high | `admin_action` | Audit + rate-limit (5/min) + OWNER/ADMIN role gate. | | `external_tools` | high | `invoke_external_tool` | MCP-tool registry. Audit + rate-limit (60/min default). | ## Allow-list semantics `TenantChatbotAccess` is a per-`(tenantId, userId)` override row. 1. Master-flag `chatbot` OFF → no capabilities. 2. Row exists with `enabled = false` → explicit deny (wins over any tenant default). 3. Row exists with `enabled = true` and non-empty `capabilityScopes` → those scopes. 4. Row exists with `enabled = true` and empty scopes → fall through to tenant default. 5. No row, `chatbotAccessMode = 'all_members'` → `Tenant.chatbotDefaultCapabilities`. 6. No row, `chatbotAccessMode = 'allow_list'` → no capabilities. ## Audit pipeline `auditChatbotToolCall` writes through `createAuditEvent`. Action format: `chatbot.tool_call..`. Payload uses an allow-list projection (`ALLOWED_ARG_KEYS`); anything not on the list is dropped so prompt-injection cannot exfiltrate secrets into the audit table. Outcomes: - `success` — inner tool ran cleanly. - `failure` — inner tool threw (rethrown to the SDK). - `blocked` — rate-limit denied or role-check returned `forbidden`. ## Rate-limit defaults (sliding window, 60 s) | Capability | Max / window | | ------------------ | ------------ | | `qa_help` | 1000 | | `kb_search` | 1000 | | `account_info` | 1000 | | `data_read` | 200 | | `data_write` | 30 | | `workflow_trigger` | 10 | | `admin_actions` | 5 | | `external_tools` | 60 | Bucket key: `chatbot:::`. Backed by the shared-store (Redis in prod, MemoryAdapter in tests). ## Operator commands ```sh # Toggle the master flag toolctrl chatbot enable | disable | status # Grant / revoke per-user access toolctrl chatbot grant --tenant acme --email alice@example.com --capabilities=qa_help,kb_search,data_read toolctrl chatbot revoke --tenant acme --email bob@example.com # disables override toolctrl chatbot revoke --tenant acme --email bob@example.com --hard # removes the row # Update tenant default toolctrl chatbot set-capabilities --tenant acme --mode allow_list \ --capabilities qa_help,kb_search,account_info ``` ## Files - `apps/web/app/modules/chatbot/lib/capabilities/{data-read,data-write,workflow-trigger,admin-actions,external-tools}.server.ts` - `apps/web/app/modules/chatbot/lib/capabilities/{audit,rate-limit,high-risk-wrapper}.server.ts` - `apps/web/app/modules/chatbot/lib/capabilities/resolver.server.ts` — V1 lookup matrix - `apps/web/app/modules/chatbot/db/chatbot-access.db.server.ts` - `apps/web/app/routes/app.$tenant/settings/chatbot.tsx` — V1 settings UI - `packages/cli/src/commands/chatbot/access.ts` - `prisma/schema/accounts.prisma` (`TenantChatbotAccess`, `Tenant.chatbotAccessMode`, `Tenant.chatbotDefaultCapabilities`) ## Troubleshooting - **A user sees no chatbot tools even with override.** Check the master flag (`toolctrl chatbot status`) and the row's `enabled` field. - **`admin_action` returns `forbidden`.** Role gate. Only OWNER / ADMIN may run admin actions even with the capability in scope. - **`rate_limited` returned from a high-risk tool.** Inspect the `chatbot:::` bucket in Redis or wait the 60 s window. ## Known limitations - No confirmation UI step yet — the wrapper is ready to return a `{ ok: false, reason: 'awaiting_confirmation' }` shape but the chat widget doesn't yet render it. - `external_tools` provider is a stub. Phase 07 V2 will wire the MCP client into it.