store: collapse migrations into a single schema.sql, drop legacy-owner path

Pre-production app, no need to preserve incremental migration history:
replace the 26 migration files with one current-state schema.sql (the
schema.sql comments are now the living documentation), simplify db.go to
apply it once instead of tracking/rebuilding through schema_migrations,
and make user_id NOT NULL everywhere now that there's no staged migration
to accommodate a nullable backfill window.

This removes the reason ClaimLegacyOwner/GENIUSRUN_LEGACY_OWNER_OIDC_SUB
existed (binding a pre-existing singleton-schema database to one account
across a staged migration), so that whole path is gone too -- the
existing dev database was wiped and reseeded fresh under the new schema.

Add cmd/dumpschema, which regenerates docs/DATABASE.md straight from the
live schema (via store.Open + sqlite_master introspection) so the
database documentation can never drift out of sync with reality.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 19:24:19 +02:00
parent 0f2101bce5
commit 644d87f1dc
37 changed files with 648 additions and 1100 deletions

View File

@@ -17,20 +17,8 @@ type User struct {
CreatedAt string
}
// CreateUser inserts a bare users row. Most callers want ProvisionUser
// instead, which also seeds the profile/taxonomy/sync-state a fresh account
// needs; CreateUser alone exists for ClaimLegacyOwner (Task 3), which
// attaches an *existing* profile/taxonomy rather than seeding new ones.
func (db *DB) CreateUser(ctx context.Context, oidcSub, displayName string) (int64, error) {
res, err := db.ExecContext(ctx, `INSERT INTO users (oidc_sub, display_name) VALUES (?, ?)`, oidcSub, displayName)
if err != nil {
return 0, fmt.Errorf("create user %q: %w", oidcSub, err)
}
return res.LastInsertId()
}
// GetUserBySub looks up a user by their OIDC subject -- the only lookup key
// the session-resolution middleware (Task 11) ever uses.
// the session-resolution middleware ever uses.
func (db *DB) GetUserBySub(ctx context.Context, oidcSub string) (User, bool, error) {
var u User
err := db.QueryRowContext(ctx, `SELECT id, oidc_sub, display_name, created_at FROM users WHERE oidc_sub = ?`, oidcSub).
@@ -45,7 +33,7 @@ func (db *DB) GetUserBySub(ctx context.Context, oidcSub string) (User, bool, err
}
// ListUsers returns every provisioned user, for the background incremental
// sync loop (Task 13) to iterate.
// sync loop to iterate.
func (db *DB) ListUsers(ctx context.Context) ([]User, error) {
rows, err := db.QueryContext(ctx, `SELECT id, oidc_sub, display_name, created_at FROM users ORDER BY id`)
if err != nil {
@@ -64,9 +52,9 @@ func (db *DB) ListUsers(ctx context.Context) ([]User, error) {
return users, rows.Err()
}
// neverMatchRule is the same placeholder every fresh install's rule-engine
// kinds start with (migration 0004) -- every activity lands in needs_review
// until the user tunes real rules.
// neverMatchRule is the placeholder every fresh install's rule-engine kinds
// start with -- every activity lands in needs_review until the user tunes
// real rules.
const neverMatchRule = `{"match":"all","conditions":[{"metric":"distance_meters","op":"<","value":0}]}`
// defaultWorkoutKindSeeds mirrors the 8 kinds migrations 0004/0007 seed for