# Store Consolidation Plan Goal: Route all database access through `lib/store.go` to enable RBAC enforcement at a single layer. ## Current State | Location | Raw SQL Ops | Notes | |----------|-------------|-------| | lib/data.go | 26 | Core domain functions (Entry, Dossier, Access) | | lib/store.go | 6 | Generic ORM layer - this becomes the single point | | lib/prompt.go | 8 | Prompt CRUD | | lib/db.go | 5 | Helper wrappers | | lib/v2.go | 1 | Cleanup utility | | api/api_access.go | 5 | Access control queries | | api/api_genome.go | 4 | Complex JOINs for variant queries | | api/api_categories.go | 7 | Schema introspection (PRAGMA) | | api/api_dossier.go | 4 | Dossier queries | | portal/main.go | 6 | Rate limiting, sessions | **Total: 76 raw SQL operations** ## Target Architecture ``` ┌─────────────────────────────────────────────────────────┐ │ API / Portal │ │ (no db.Query, db.Exec - only store.* calls) │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ lib/store.go │ │ ┌─────────────────────────────────────────────────┐ │ │ │ RBAC Check Layer │ │ │ │ - Check accessor permissions before query │ │ │ │ - Filter results based on access grants │ │ │ │ - Cache permissions per accessor-dossier pair │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Data Layer │ │ │ │ - Auto-encrypt on write │ │ │ │ - Auto-decrypt on read │ │ │ │ - Reflection-based struct mapping │ │ │ └─────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────┐ │ SQLite │ └─────────────┘ ``` ## Migration Phases ### Phase 1: Consolidate lib/ (Week 1) Move all SQL from lib/*.go into store.go patterns. **1.1 - lib/data.go Entry functions → store.go** - [ ] `EntryAdd()` → `store.Insert(entry, accessorID)` - [ ] `EntryUpdate()` → `store.Update(entry, accessorID)` - [ ] `EntryDelete()` → `store.Delete(entry, accessorID)` - [ ] `EntryGet()` → `store.Get(entry, accessorID)` - [ ] `EntryList()` → `store.List(filter, accessorID)` - [ ] `EntryAddBatch()` → `store.InsertBatch(entries, accessorID)` **1.2 - lib/data.go Dossier functions → store.go** - [ ] `DossierWrite()` → `store.Insert/Update(dossier, accessorID)` - [ ] `DossierGet()` → `store.Get(dossier, accessorID)` - [ ] `DossierGetByEmail()` → `store.GetBy(dossier, "email", accessorID)` - [ ] `DossierList()` → `store.List(filter, accessorID)` **1.3 - lib/data.go Access functions → store.go** - [ ] `AccessWrite()` → `store.GrantAccess(accessor, target, perms)` - [ ] `AccessList()` → `store.ListAccess(dossierID)` - [ ] `AccessRevoke()` → `store.RevokeAccess(accessor, target)` **1.4 - lib/prompt.go → store.go** - [ ] Move prompt CRUD to store patterns - [ ] Prompts are user-scoped, add accessor checks **1.5 - lib/db.go** - [ ] Keep DBInit/DBClose/DB() - [ ] Remove DBExec, DBInsert, DBUpdate, DBDelete, DBQuery, DBQueryRow - [ ] These become internal to store.go only ### Phase 2: Consolidate api/ (Week 2) Replace raw SQL in API handlers with store calls. **2.1 - api/api_access.go (5 ops)** - [ ] SELECT dossier_access → `store.ListAccess()` - [ ] INSERT dossier_access → `store.GrantAccess()` - [ ] UPDATE accessed_at → `store.TouchAccess()` - [ ] DELETE dossier_access → `store.RevokeAccess()` **2.2 - api/api_genome.go (4 ops)** - [ ] Complex JOIN queries need special handling - [ ] Option A: Add `store.QueryGenome(filter, accessorID)` specialized method - [ ] Option B: Build query in store, return filtered results - [ ] Must preserve tier-matching logic **2.3 - api/api_categories.go (7 ops)** - [ ] PRAGMA queries for schema info - keep as utility - [ ] Category listing → `store.ListCategories()` **2.4 - api/api_dossier.go (4 ops)** - [ ] All should route through `store.Get/Update(dossier, accessorID)` ### Phase 3: Consolidate portal/ (Week 2) **3.1 - portal/main.go (6 ops)** - [ ] Session table: CREATE IF NOT EXISTS - keep in init - [ ] Rate limiting: separate concern, could stay separate or move to store - [ ] Session cleanup: utility function **3.2 - Unify db connection** - [ ] Portal currently opens its own connection - [ ] Should use lib.DB() like API does - [ ] Single connection pool for both ### Phase 4: Add RBAC Layer (Week 3) Once all access goes through store.go: **4.1 - Permission cache** ```go type permCache struct { mu sync.RWMutex cache map[string]*permEntry // key: "accessor:target" ttl time.Duration } type permEntry struct { perms uint8 // rwdm bitmask expires time.Time } ``` **4.2 - Check functions** ```go func (s *Store) canRead(accessorID, targetDossierID, entryID string) bool func (s *Store) canWrite(accessorID, targetDossierID, entryID string) bool func (s *Store) canDelete(accessorID, targetDossierID, entryID string) bool func (s *Store) canManage(accessorID, targetDossierID string) bool ``` **4.3 - Integrate checks** - Every store.Get/List filters by accessor permissions - Every store.Insert/Update checks write permission - Every store.Delete checks delete permission - Access grants require manage permission ## Store API Design ```go // Context carries accessor identity type StoreContext struct { AccessorID string // who is making the request BypassRBAC bool // for system operations only } // Main store interface func Get[T any](ctx StoreContext, id string) (*T, error) func List[T any](ctx StoreContext, filter Filter) ([]*T, error) func Insert[T any](ctx StoreContext, item *T) error func Update[T any](ctx StoreContext, item *T) error func Delete[T any](ctx StoreContext, id string) error // Access-specific func GrantAccess(ctx StoreContext, grant AccessGrant) error func RevokeAccess(ctx StoreContext, accessor, target string) error func ListAccess(ctx StoreContext, dossierID string) ([]AccessGrant, error) ``` ## Files to Modify ### High Priority (direct SQL) 1. `lib/data.go` - Refactor 26 functions to use store internally 2. `lib/prompt.go` - Refactor 8 functions 3. `api/api_access.go` - Replace 5 raw queries 4. `api/api_genome.go` - Replace 4 raw queries 5. `api/api_dossier.go` - Replace 4 raw queries ### Medium Priority 6. `api/api_categories.go` - 7 queries (some are PRAGMA) 7. `portal/main.go` - 6 queries (sessions, rate limiting) 8. `lib/db.go` - Remove public query helpers ### Low Priority (already clean) - `api/api_entries.go` - Uses lib functions - `api/api_v1.go` - Uses lib functions - `api/api_studies.go` - Uses lib functions - etc. ## Migration Strategy 1. **Don't break existing code** - Keep old function signatures, change internals 2. **Add accessor parameter gradually** - Start with optional, make required later 3. **Test each phase** - Deploy after each phase, verify nothing breaks 4. **RBAC off by default** - Add checks but disable until fully migrated ## Success Criteria - [ ] Zero `db.Query/Exec` calls outside lib/store.go - [ ] All data access includes accessor identity - [ ] Permission cache working with TTL - [ ] RBAC checks enforced at store layer - [ ] Audit log captures all access attempts