8.6 KiB
8.6 KiB
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
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
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
// 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)
lib/data.go- Refactor 26 functions to use store internallylib/prompt.go- Refactor 8 functionsapi/api_access.go- Replace 5 raw queriesapi/api_genome.go- Replace 4 raw queriesapi/api_dossier.go- Replace 4 raw queries
Medium Priority
api/api_categories.go- 7 queries (some are PRAGMA)portal/main.go- 6 queries (sessions, rate limiting)lib/db.go- Remove public query helpers
Low Priority (already clean)
api/api_entries.go- Uses lib functionsapi/api_v1.go- Uses lib functionsapi/api_studies.go- Uses lib functions- etc.
Migration Strategy
- Don't break existing code - Keep old function signatures, change internals
- Add accessor parameter gradually - Start with optional, make required later
- Test each phase - Deploy after each phase, verify nothing breaks
- RBAC off by default - Add checks but disable until fully migrated
Success Criteria
- Zero
db.Query/Execcalls 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