This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/DESIGN-0.17.0.md
2026-02-27 16:25:39 +00:00

185 lines
5.7 KiB
Markdown

# 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'`)