Artifact c8b5a71e3056b172365e289d644067477c5d27f8592367d7a93f9946e8065851:
- File
internal/oltp/oltp.go
— part of check-in
[be3686ccbe]
at
2026-08-22 14:19:19
on branch fossil-caps
— caps phase 1: fossil cap strings in OLTP, EffectiveCaps expansion, roles→caps migration
RFC 0004 §3/§5. v never implies u; a excludes s x y; s is universal; blank nobody+anonymous = private. Migration runs on every open, idempotent, never clobbers explicit caps. (user: dmestas, size: 3978) [annotate] [blame] [check-ins using] [more...]
// Package oltp is quarry's forge OLTP database (0001 §8): the only // authoritative non-DAG state. v1 holds sync users — per-repo login // credentials for push gating. Small by construction; the litestream // replica is the recovery story when deployment warrants it. package oltp import ( "database/sql" "fmt" "strings" "github.com/ncruces/go-sqlite3/driver" ) const schema = ` CREATE TABLE IF NOT EXISTS sync_user ( repo TEXT NOT NULL, user TEXT NOT NULL, secret TEXT NOT NULL, -- RFC §6.3 shared secret (40 hex); plaintext never stored PRIMARY KEY (repo, user) );` type DB struct { db *sql.DB } func Open(path string) (*DB, error) { db, err := driver.Open("file:" + path) if err != nil { return nil, err } if _, err := db.Exec(`PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;`); err != nil { db.Close() return nil, fmt.Errorf("oltp: pragmas: %w", err) } if _, err := db.Exec(schema); err != nil { db.Close() return nil, fmt.Errorf("oltp: schema: %w", err) } if _, err := db.Exec(accountSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: account schema: %w", err) } if _, err := db.Exec(webhookSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: webhook schema: %w", err) } if _, err := db.Exec(sessionSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: session schema: %w", err) } if _, err := db.Exec(ciSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: ci schema: %w", err) } if _, err := db.Exec(mirrorSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: mirror schema: %w", err) } if _, err := db.Exec(repoSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: repo schema: %w", err) } if _, err := db.Exec(voteSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: vote schema: %w", err) } if _, err := db.Exec(categorySchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: category schema: %w", err) } if _, err := db.Exec(pollSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: poll schema: %w", err) } if _, err := db.Exec(moderationSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: moderation schema: %w", err) } if _, err := db.Exec(subsSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: subs schema: %w", err) } if _, err := db.Exec(capsSchema); err != nil { db.Close() return nil, fmt.Errorf("oltp: caps schema: %w", err) } for _, alter := range append(append(repoMigrations, mirrorMigrations...), accountMigrations...) { if _, err := db.Exec(alter); err != nil && !strings.Contains(err.Error(), "duplicate column") { db.Close() return nil, fmt.Errorf("oltp: migrate: %w", err) } } out := &DB{db: db} // Legacy role rows become cap strings on every open — idempotent, // and explicit caps are never clobbered (RFC 0004 §5). if err := out.MigrateRolesToCaps(); err != nil { db.Close() return nil, fmt.Errorf("oltp: roles→caps: %w", err) } return out, nil } func (d *DB) Close() error { return d.db.Close() } // SetSyncUser stores (replacing) one repo user's shared secret. func (d *DB) SetSyncUser(repo, user, secret string) error { _, err := d.db.Exec(`INSERT OR REPLACE INTO sync_user (repo, user, secret) VALUES (?, ?, ?)`, repo, user, secret) return err } // Secret returns the stored shared secret for one repo user. func (d *DB) Secret(repo, user string) (string, bool) { var s string err := d.db.QueryRow(`SELECT secret FROM sync_user WHERE repo = ? AND user = ?`, repo, user).Scan(&s) return s, err == nil } // Gated reports whether a repo has any sync users — the v1 policy // switch: no users means anonymous push stays allowed (local use), // one or more users means pushes must authenticate. func (d *DB) Gated(repo string) bool { var n int if err := d.db.QueryRow(`SELECT count(*) FROM sync_user WHERE repo = ?`, repo).Scan(&n); err != nil { return false } return n > 0 }