# DESIGN-0.17.0 — Persona-KB Binding + Enterprise KB Mode ## Overview Personas become **gateways** to knowledge. In enterprise deployments, users don't interact with KBs directly — they talk to Personas that have KBs attached. This is the core differentiator for enterprise use. Depends on: knowledge bases (v0.14.0), user groups (v0.16.0). **Design principle: Personas carry context, not users.** When a user selects a Persona with bound KBs, the KB context flows automatically — no manual KB selection, no toggle management, no confusion about which KBs are in scope. Admins curate the KB↔Persona relationships; users just pick a Persona. --- ## 1. Schema ### `persona_knowledge_bases` (new join table) ```sql CREATE TABLE persona_knowledge_bases ( persona_id UUID NOT NULL REFERENCES personas(id) ON DELETE CASCADE, kb_id UUID NOT NULL REFERENCES knowledge_bases(id) ON DELETE CASCADE, auto_search BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (persona_id, kb_id) ); ``` `auto_search` controls whether the KB is searched automatically on every message (top-K results prepended to context) or only via explicit `kb_search` tool calls. Default `false` = tool-only. ### `knowledge_bases.discoverable` (new column) ```sql ALTER TABLE knowledge_bases ADD COLUMN discoverable BOOLEAN NOT NULL DEFAULT true; ``` When `false`, the KB does not appear in user-facing listings (`ListDiscoverableKBs`). It remains searchable through Persona bindings. ### `platform_policies.kb_direct_access` (new row) Seeded as `'true'` (permissive default). When set to `'false'`, the channel KB popup is hidden — users access KBs exclusively through Persona bindings. --- ## 2. Store Layer ### `PersonaStore` additions - `SetKBs(ctx, personaID, kbIDs, autoSearch)` — UPSERT join table - `GetKBs(ctx, personaID)` — returns `[]PersonaKB` with KB metadata ### `KnowledgeBaseStore` additions - `ListDiscoverable(ctx, userID, teamIDs)` — KBs where `discoverable=true` and user has access via ownership, team, or global scope - `SetDiscoverable(ctx, kbID, discoverable)` — toggle visibility - `UpdateDocumentStorageKey(ctx, docID, key)` — moved from raw SQL --- ## 3. Completion Pipeline ### KB scoping in `BuildKBHint` When a Persona has bound KBs, `BuildKBHint` merges them with any channel-attached KBs: 1. Load channel KBs (existing path) 2. Load Persona KBs via `PersonaStore.GetKBs()` 3. Union the two sets (deduplicated by KB ID) 4. Build the hint string with listing ### Persona ID threading The `personaID` is threaded through the completion handler methods: `Complete()` → `streamCompletion(…, personaID)` / `syncCompletion(…, personaID)` → `loadConversation(…, personaID)`. The `ExecutionContext` for tool calls carries the Persona ID so `kb_search` can auto-include Persona-bound KBs in its search scope. --- ## 4. Role Fallback Alerts (issue #69) When a role's primary provider fails: 1. **Attempt fallback** — existing behavior, unchanged. 2. **Emit event** — `role.fallback` on EventBus (DirToClient). 3. **Cooldown** — 5-minute per-role TTL prevents flooding. 4. **Admin toast** — frontend listens for `role.fallback` events, shows warning/error toast to admin users only. ### Resolver changes ```go type Resolver struct { stores store.Stores vault *crypto.KeyResolver bus *events.Bus mu sync.Mutex cooldown map[string]time.Time // role → last alert time } ``` `WithBus(bus)` builder method. Nil-safe — if no bus is attached, alerts are suppressed (log only). --- ## 5. Security Fixes ### ResolvePreset bypass `ResolvePreset()` in `presets.go` used raw SQL that skipped the `resource_grants` table added in v0.16.0. A Persona accessible only via group grant would fail at completion time. Fixed to use `PersonaStore.GetByID()` + `UserCanAccess()`. ### KB create scope authorization `POST /api/v1/knowledge-bases` now enforces: - `scope=global` → requires admin role - `scope=team` → requires team admin role for the target team - `scope=personal` → open to all authenticated users --- ## 6. Frontend ### KB Picker (`persona-kb.js`) Standalone component: `renderPersonaKBPicker(container, opts)` → control object with `getValues()`, `setValues()`, `clear()`, `refresh()`. Options: `scope` ('admin'|'team'|'personal'), `teamId`, `prefix`. Shows checkboxes for available KBs with doc/chunk counts. Selected KBs show an "Auto-inject" toggle for the `auto_search` flag. ### Integration points - Admin preset form: KB picker below the form, load/save bindings on edit/create via `/api/v1/admin/presets/:id/knowledge-bases`. - Team preset form: same pattern via team API prefix. - User preset form: shows discoverable KBs only. - Admin settings: `kb_direct_access` toggle checkbox. ### Fallback alert Admin users receive `UI.toast()` notifications when `role.fallback` events arrive via WebSocket. --- ## 7. API Endpoints (new) | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/presets/:id/knowledge-bases` | List KBs bound to a persona | | PUT | `/api/v1/presets/:id/knowledge-bases` | Set persona-KB bindings | | GET | `/api/v1/admin/presets/:id/knowledge-bases` | Admin: list persona KBs | | PUT | `/api/v1/admin/presets/:id/knowledge-bases` | Admin: set persona-KB bindings | | GET | `/api/v1/knowledge-bases-discoverable` | List discoverable KBs for user | | PUT | `/api/v1/knowledge-bases/:id/discoverable` | Toggle KB discoverability | --- ## 8. Migration Single migration file: `002_v017_persona_kb.sql` - Creates `persona_knowledge_bases` table - Adds `discoverable` column to `knowledge_bases` - Seeds `kb_direct_access` platform policy (default `'true'`)