inou/docs/store-consolidation-plan.md

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)

  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

  1. api/api_categories.go - 7 queries (some are PRAGMA)
  2. portal/main.go - 6 queries (sessions, rate limiting)
  3. 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